org.talend.esb.monitoring.hq.DynamicMxFieldMeasurementPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.talend.esb.monitoring.hq.DynamicMxFieldMeasurementPlugin.java

Source

/*
 * #%L
 * Talend ESB :: Adapters :: HQ :: Common
 * %%
 * Copyright (C) 2011 - 2013 Talend Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package org.talend.esb.monitoring.hq;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;

import org.apache.commons.logging.Log;
import org.hyperic.hq.product.Metric;
import org.hyperic.hq.product.MetricInvalidException;
import org.hyperic.hq.product.MetricNotFoundException;
import org.hyperic.hq.product.MetricUnreachableException;
import org.hyperic.hq.product.MetricValue;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.PluginManager;
import org.hyperic.hq.product.jmx.MxMeasurementPlugin;
import org.hyperic.hq.product.jmx.MxUtil;

import static org.talend.esb.monitoring.hq.HypericUtils.getOptionalProperty;
import static org.talend.esb.monitoring.hq.HypericUtils.getMandatoryProperty;

/**
 * This class implements a measurement plug-in for a Hyperic, that provides
 * metric measurement for JMX services with a dynamic name. Provides also
 * caching for object names.
 * 
 * @author Eugene Tarasov
 */
public class DynamicMxFieldMeasurementPlugin extends MxMeasurementPlugin {

    private final Log log = getLog();

    /**
     * Hyperic initializes the measurement plug-in as many times as many
     * platforms it supports. We use this property to make sure we print
     * initialization logs only for the first instance (because for all other
     * instances it's the same).
     */
    private static final AtomicBoolean isFirstInstance = new AtomicBoolean(true);

    private static final int DEFAULT_GC_INTERVAL = 24 * 60 * 60 * 1000;

    private static final String MSG_ERR_CONNECT = "Cannot find the new MBean name because of the connection problems.";
    private static final String MSG_ERR_NOTFOUND = "ObjectName has not been found.";
    private static final String MSG_ERR_MALFORMED = "Provided name has wrong format.";
    private static final String MSG_ERR_UNEXPECTED = "Unexpected problem during looking up an ObjectName.";

    private String targetDomain;

    private final TrivialCache cache = new TrivialCache();

    /**
     * {@inheritDoc}
     */
    @Override
    public void init(PluginManager manager) throws PluginException {
        super.init(manager);

        targetDomain = getMandatoryProperty(this, TalendHqConstants.PROP_TARGET_DOMAIN) + ":";

        boolean firstInstance = isFirstInstance.getAndSet(false);

        final String gcIntervalStr = getOptionalProperty(this, TalendHqConstants.PROP_GC_INTERVAL);
        if (gcIntervalStr == null) {
            logInfoOnlyFirstInstance(firstInstance,
                    "No explicit cache periodical cleanup interval specified, scheduling it to happen every ",
                    DEFAULT_GC_INTERVAL, " ms by default.");
            cache.scheduleGc(DEFAULT_GC_INTERVAL);
        } else {
            try {
                final long interval = Long.valueOf(gcIntervalStr);
                if (interval > 0) {
                    logInfoOnlyFirstInstance(firstInstance, "Scheduling cache periodical cleanup to happen every ",
                            interval, " ms.");
                    cache.scheduleGc(interval);
                } else {
                    logInfoOnlyFirstInstance(firstInstance, "Cache periodical cleanup is switched off.");
                }
            } catch (NumberFormatException e) {
                throw new PluginException("Cannot read cache gc interval value.", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void shutdown() throws PluginException {
        super.shutdown();
        cache.clear();
    }

    /**
     * Looks up for a ObjectName based on a given pattern ObjectName.
     * 
     * @param patternObjectName
     *            a pattern object name
     * @param connectionProps
     *            connection properties
     * @return Returns the found ObjectName, otherwise throws an exception
     * @throws MetricNotFoundException
     *             is thrown when no metric was found using specified pattern
     * @throws MetricUnreachableException
     *             is thrown when it is impossible to find the object name
     *             because of connection problems
     * @throws MetricInvalidException
     *             is thrown if patter format is wrong
     * @throws PluginException
     *             if some unexpected problem happened
     */
    private String findObjectName(final String patternObjectName, final Properties connectionProps)
            throws MetricNotFoundException, MetricUnreachableException, MetricInvalidException, PluginException {

        ObjectName patternOn;
        try {
            patternOn = new ObjectName(patternObjectName);
        } catch (MalformedObjectNameException e) {
            throw new MetricInvalidException(MSG_ERR_MALFORMED, e);
        }

        JMXConnector jmxConnector = null;
        try {
            jmxConnector = MxUtil.getCachedMBeanConnector(connectionProps);
            final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection();
            final Set<ObjectName> result = conn.queryNames(patternOn, null);

            if (result.iterator().hasNext()) {
                return result.iterator().next().getCanonicalName();
            }
        } catch (IOException e) {
            log.debug(MSG_ERR_CONNECT, e);
            throw new MetricUnreachableException(MSG_ERR_CONNECT, e);
        } catch (Exception e) {
            log.debug(MSG_ERR_UNEXPECTED, e);
            throw new PluginException(MSG_ERR_UNEXPECTED, e);
        } finally {
            // it's null-proof
            MxUtil.close(jmxConnector);
        }

        throw new MetricNotFoundException(MSG_ERR_NOTFOUND);
    }

    /**
     * {@inheritDoc}
     */
    public MetricValue getValue(final Metric metric)
            throws PluginException, MetricNotFoundException, MetricUnreachableException, MetricInvalidException {

        final String origObjectName = metric.getObjectName();
        final String oldObjectName = Metric.decode(origObjectName);

        // no lookup for non-supported domain
        if (!oldObjectName.startsWith(targetDomain)) {
            return super.getValue(metric);
        }

        // a simple test for static object name
        if (!oldObjectName.contains("*")) {
            return super.getValue(metric);
        }

        boolean fromCache;
        String newObjectName;

        if (cache.contains(oldObjectName)) {
            newObjectName = cache.get(oldObjectName);
            fromCache = true;
        } else {
            try {
                newObjectName = findObjectName(oldObjectName, metric.getProperties());
                fromCache = false;
                cache.put(oldObjectName, newObjectName);
            } catch (MetricUnreachableException e) {
                if (metric.isAvail()) {
                    return new MetricValue(Metric.AVAIL_DOWN);
                }
                throw e;
            }
        }

        try {
            metric.setObjectName(newObjectName);

            try {
                return super.getValue(metric);
            } catch (MetricNotFoundException e) {
                if (!(e.getCause() instanceof InstanceNotFoundException) || !fromCache) {
                    throw e;
                }

                cache.invalidate(oldObjectName);

                try {
                    newObjectName = findObjectName(oldObjectName, metric.getProperties());
                } catch (MetricUnreachableException e2) {
                    if (metric.isAvail()) {
                        return new MetricValue(Metric.AVAIL_DOWN);
                    }
                    throw e2;
                }

                cache.put(oldObjectName, newObjectName);
                metric.setObjectName(newObjectName);

                return super.getValue(metric);
            }
        } finally {
            // hyperic caches metrics, so we must restore original object name
            // but only if object name was changed
            if (metric.getObjectName() != origObjectName) {
                metric.setObjectName(origObjectName);
            }
        }
    }

    /**
     * This class implements cache for keeping discovered dynamic object names.
     * It provides a simple GC-like daemon for cleaning unused dynamic names.
     * It's methods are synchronized.
     */
    static final class TrivialCache {
        private volatile Map<String, Entry> store = new HashMap<String, Entry>();
        private final Object lock = new Object();
        private final Timer gcTimer = new Timer();

        /**
         * Stops the GC and clears the cache store. The cache cannot be used
         * after shutting it down.
         */
        public void shutdown() {
            gcTimer.cancel();
            gcTimer.purge();
            store.clear();
            store = null;
        }

        /**
         * @param key
         *            cached entry key
         * @return Stored value, or null if no entry with specified key exists
         */
        public String get(String key) {
            final Entry result = store.get(key);
            if (result == null) {
                return null;
            }
            if (result.outdated) {
                synchronized (lock) {
                    result.outdated = false;
                }
            }
            return result.value;
        }

        /**
         * Checks whether anything is cached with the specified key.
         * 
         * @param key
         *            a key to check
         * @return true if there is a cache entry with specified key, false
         *         otherwise
         * @throws NullPointerException
         *             if key is null
         */
        public boolean contains(final String key) throws NullPointerException {
            if (key == null) {
                throw new NullPointerException("A key cannot be null.");
            }
            return store.containsKey(key);
        }

        /**
         * @return count of entries in the cache
         */
        public int size() {
            return store.size();
        }

        /**
         * Puts a new entry into the cache. If an entry with specified key
         * already exists, it will be overwritten.
         * 
         * @param key
         *            a key of the entry
         * @param value
         *            value to store in the cache
         */
        public void put(final String key, final String value) {
            synchronized (lock) {
                store.put(key, new Entry(value, false));
            }
        }

        /**
         * Invalidates (removes) an entry with specified key. If there is no an
         * entry with specified key, then nothing happens.
         * 
         * @param key
         *            a key of an entry to invalidate
         */
        public void invalidate(final String key) {
            synchronized (lock) {
                store.remove(key);
            }
        }

        /**
         * Removes all entries for the cache.
         */
        public void clear() {
            synchronized (lock) {
                store.clear();
            }
        }

        /**
         * Schedules a GC-like daemon that removes unused entries from the
         * cache.
         * 
         * @param period
         *            a period of time (in milliseconds) between checks
         */
        public void scheduleGc(long period) {
            gcTimer.schedule(new GcTask(), period, period);
        }

        // stops garbage collector
        void stopGc() {
            gcTimer.cancel();
        }

        /**
         * Garbage Collector task.
         */
        private class GcTask extends TimerTask {
            @Override
            public void run() {
                final Map<String, Entry> newStore = new HashMap<String, Entry>();

                synchronized (lock) {
                    if (store.isEmpty()) {
                        return;
                    }

                    for (String key : store.keySet()) {
                        final Entry entry = store.get(key);

                        if (!entry.outdated) {
                            newStore.put(key, new Entry(entry.value, true));
                        }
                    }

                    final Map<String, Entry> oldStore = store;
                    store = newStore;
                    oldStore.clear();
                }
            }
        }

        /**
         * This class represents a cache entry.
         */
        private static class Entry {
            final String value;
            volatile boolean outdated;

            public Entry(final String value, boolean outdated) {
                this.value = value;
                this.outdated = outdated;
            }
        }

    }

    // logging helper method, to prevent unnecessary string concatenation
    private void logInfo(final Object o1, final Object... info) {
        if (!log.isInfoEnabled()) {
            return;
        }

        if (info.length == 0) {
            log.info(o1.toString());
            return;
        }

        final StringBuilder sb = new StringBuilder(o1.toString());
        for (final Object o : info) {
            sb.append(o.toString());
        }

        log.info(sb.toString());
    }

    private void logInfoOnlyFirstInstance(boolean firstInstance, final Object o1, final Object... info) {
        if (firstInstance) {
            logInfo(o1, info);
        }
    }
}