hello.MetricsActivator.java Source code

Java tutorial

Introduction

Here is the source code for hello.MetricsActivator.java

Source

package hello;

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * 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.
 */

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import javax.management.DynamicMBean;
import javax.management.JMException;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.BeansException;
import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.Lifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.channel.management.AbstractMessageChannelMetrics;
import org.springframework.integration.channel.management.MessageChannelMetrics;
import org.springframework.integration.channel.management.PollableChannelManagement;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.management.MessageSourceMetrics;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.handler.management.AbstractMessageHandlerMetrics;
import org.springframework.integration.handler.management.MessageHandlerMetrics;
import org.springframework.integration.history.MessageHistoryConfigurer;
import org.springframework.integration.history.TrackableComponent;
import org.springframework.integration.monitor.DefaultMetricsFactory;
import org.springframework.integration.monitor.LifecycleMessageHandlerMetrics;
import org.springframework.integration.monitor.LifecycleMessageSourceMetrics;
import org.springframework.integration.monitor.LifecycleTrackableMessageHandlerMetrics;
import org.springframework.integration.monitor.LifecycleTrackableMessageSourceMetrics;
import org.springframework.integration.monitor.MetricsFactory;
import org.springframework.integration.monitor.RouterMetrics;
import org.springframework.integration.monitor.TrackableRouterMetrics;
import org.springframework.integration.router.MappingMessageRouterManagement;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.integration.support.management.ConfigurableMetricsAware;
import org.springframework.integration.support.management.IntegrationManagedResource;
import org.springframework.integration.support.management.Statistics;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.UnableToRegisterMBeanException;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
import org.springframework.jmx.export.metadata.InvalidMetadataException;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.JmxUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

/**
 * <p>
 * MBean exporter for Spring Integration components in an existing application. Add an instance of this as a bean
 * definition in the same context as the components you need to monitor and all message channels and message handlers
 * will be exposed.
 * </p>
 * <p>
 * Channels will report metrics on send and receive (counts, rates, errors) and handlers will report metrics on
 * execution duration. Channels will be registered under their name (bean id), if explicit, or the last part of their
 * internal name (e.g. "nullChannel") if registered by the framework. A handler that is attached to an endpoint will be
 * registered with the endpoint name (bean id) if there is one, otherwise under the name of the input channel. Handler
 * object names contain a <code>bean</code> key that reports the source of the name: "endpoint" if the name is the
 * endpoint id; "anonymous" if it is the input channel; and "handler" as a fallback, where the object name is just the
 * <code>toString()</code> of the handler.
 * </p>
 * <p>
 * This component is itself an MBean, reporting attributes concerning the names and object names of the channels and
 * handlers. It doesn't register itself to avoid conflicts with the standard <code>&lt;context:mbean-export/&gt;</code>
 * from Spring (which should therefore be used any time you need to expose those features).
 * </p>
 *
 * @author Dave Syer
 * @author Helena Edelson
 * @author Oleg Zhurakousky
 * @author Gary Russell
 * @author Artem Bilan
 */
public class MetricsActivator extends MBeanExporter implements ApplicationContextAware, EmbeddedValueResolverAware {

    private static final Log logger = LogFactory.getLog(MetricsActivator.class);

    public static final String DEFAULT_DOMAIN = "org.springframework.integration";

    private final AnnotationJmxAttributeSource attributeSource = new IntegrationJmxAttributeSource();

    private ApplicationContext applicationContext;

    private final Map<Object, AtomicLong> anonymousHandlerCounters = new HashMap<Object, AtomicLong>();

    private final Map<Object, AtomicLong> anonymousSourceCounters = new HashMap<Object, AtomicLong>();

    private final Set<MessageHandlerMetrics> handlers = new HashSet<MessageHandlerMetrics>();

    private final Set<MessageSourceMetrics> sources = new HashSet<MessageSourceMetrics>();

    private final Set<Lifecycle> inboundLifecycleMessageProducers = new HashSet<Lifecycle>();

    private final Set<MessageChannelMetrics> channels = new HashSet<MessageChannelMetrics>();

    private final Map<String, MessageChannelMetrics> channelsByName = new HashMap<String, MessageChannelMetrics>();

    private final Map<String, MessageHandlerMetrics> handlersByName = new HashMap<String, MessageHandlerMetrics>();

    private final Map<String, MessageSourceMetrics> sourcesByName = new HashMap<String, MessageSourceMetrics>();

    private final Map<String, MessageChannelMetrics> allChannelsByName = new HashMap<String, MessageChannelMetrics>();

    private final Map<String, MessageHandlerMetrics> allHandlersByName = new HashMap<String, MessageHandlerMetrics>();

    private final Map<String, MessageSourceMetrics> allSourcesByName = new HashMap<String, MessageSourceMetrics>();

    private final Map<String, String> beansByEndpointName = new HashMap<String, String>();

    private String domain = DEFAULT_DOMAIN;

    private final Properties objectNameStaticProperties = new Properties();

    private final MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler(attributeSource);

    private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy(attributeSource);

    private String[] componentNamePatterns = { "*" };

    private String[] enabledCountsPatterns = { "*" };

    private String[] enabledStatsPatterns = { "*" };

    private volatile long shutdownDeadline;

    private final AtomicBoolean shuttingDown = new AtomicBoolean();

    private StringValueResolver embeddedValueResolver;

    private MetricsFactory metricsFactory = new DefaultMetricsFactory();

    public MetricsActivator() {
        super();
        // Shouldn't be necessary, but to be on the safe side...
        setAutodetect(false);
        setNamingStrategy(defaultNamingStrategy);
        setAssembler(assembler);
    }

    @Override
    public void afterPropertiesSet() {
        // If no server was provided then try to find one. This is useful in an environment
        // where there is already an MBeanServer loaded.
        if (this.server == null) {
            this.server = JmxUtils.locateMBeanServer();
        }
    }

    /**
     * Static properties that will be added to all object names.
     *
     * @param objectNameStaticProperties the objectNameStaticProperties to set
     */
    public void setObjectNameStaticProperties(Map<String, String> objectNameStaticProperties) {
        this.objectNameStaticProperties.putAll(objectNameStaticProperties);
    }

    /**
     * The JMX domain to use for MBeans registered. Defaults to <code>spring.application</code> (which is useful in
     * SpringSource HQ).
     *
     * @param domain the domain name to set
     */
    public void setDefaultDomain(String domain) {
        this.domain = domain;
        this.defaultNamingStrategy.setDefaultDomain(domain);
    }

    /**
     * Set the array of simple patterns for component names to register (defaults to '*').
     * The pattern is applied to all components before they are registered, looking for a
     * match on the 'name' property of the ObjectName. A MessageChannel and a
     * MessageHandler (for instance) can share a name because they have a different type,
     * so in that case they would either both be included or both excluded. Since version
     * 4.2, a leading '!' negates the pattern match ('!foo*' means don't export components
     * where the name matches the pattern 'foo*'). For components with names that match
     * multiple patterns, the first pattern wins.
     * @param componentNamePatterns the patterns.
     */
    public void setComponentNamePatterns(String[] componentNamePatterns) {
        Assert.notEmpty(componentNamePatterns, "componentNamePatterns must not be empty");
        this.componentNamePatterns = Arrays.copyOf(componentNamePatterns, componentNamePatterns.length);
    }

    /**
     * Set the array of simple patterns for component names for which message counts will
     * be enabled (defaults to '*'). Only patterns that also match
     * {@link #setComponentNamePatterns(String[]) componentNamePatterns} will be
     * considered. Enables message counting (`sendCount`, `errorCount`, `receiveCount`)
     * for those components that support counters (channels, message handlers, etc).
     * This is the initial setting only, individual components can have counts
     * enabled/disabled at runtime. May be overridden by an entry in
     * {@link #setEnabledStatsPatterns(String[]) enabledStatsPatterns} which is additional
     * functionality over simple counts. If a pattern starts with `!`, counts are disabled
     * for matches. For components that match multiple patterns, the first pattern wins.
     * Disabling counts at runtime also disables stats.
     * @param enabledCountsPatterns the patterns.
     * @since 4.2
     */
    public void setEnabledCountsPatterns(String[] enabledCountsPatterns) {
        Assert.notEmpty(enabledCountsPatterns, "enabledCountsPatterns must not be empty");
        this.enabledCountsPatterns = Arrays.copyOf(enabledCountsPatterns, enabledCountsPatterns.length);
    }

    /**
     * Set the array of simple patterns for component names for which message statistics
     * will be enabled (response times, rates etc), as well as counts (a positive match
     * here overrides {@link #setEnabledCountsPatterns(String[]) enabledCountsPatterns},
     * you can't have statistics without counts). (defaults to '*'). Only patterns that
     * also match {@link #setComponentNamePatterns(String[]) componentNamePatterns} will
     * be considered. Enables statistics for those components that support statistics
     * (channels - when sending, message handlers, etc). This is the initial setting only,
     * individual components can have stats enabled/disabled at runtime. If a pattern
     * starts with `!`, stats (and counts) are disabled for matches. Note: this means that
     * '!foo' here will disable stats and counts for 'foo' even if counts are enabled for
     * 'foo' in {@link #setEnabledCountsPatterns(String[]) enabledCountsPatterns}. For
     * components that match multiple patterns, the first pattern wins. Enabling stats at
     * runtime also enables counts.
     * @param enabledStatsPatterns the patterns.
     * @since 4.2
     */
    public void setEnabledStatsPatterns(String[] enabledStatsPatterns) {
        Assert.notEmpty(enabledStatsPatterns, "componentNamePatterns must not be empty");
        this.enabledStatsPatterns = Arrays.copyOf(enabledStatsPatterns, enabledStatsPatterns.length);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Assert.notNull(applicationContext, "ApplicationContext may not be null");
        this.applicationContext = applicationContext;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    /**
     * Set a metrics factory.
     * @param metricsFactory the factory.
     * @since 4.2
     */
    public void setMetricsFactory(MetricsFactory metricsFactory) {
        this.metricsFactory = metricsFactory;
    }

    @Override
    public void afterSingletonsInstantiated() {
        Map<String, MessageHandlerMetrics> messageHandlers = this.applicationContext
                .getBeansOfType(MessageHandlerMetrics.class);
        for (Entry<String, MessageHandlerMetrics> entry : messageHandlers.entrySet()) {
            String beanName = entry.getKey();
            MessageHandlerMetrics bean = entry.getValue();
            if (this.handlerInAnonymousWrapper(bean) != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping " + beanName + " because it wraps another handler");
                }
                continue;
            }
            // If the handler is proxied, we have to extract the target to expose as an MBean.
            // The MetadataMBeanInfoAssembler does not support JDK dynamic proxies.
            MessageHandlerMetrics monitor = (MessageHandlerMetrics) extractTarget(bean);
            this.handlers.add(monitor);
        }

        Map<String, MessageSourceMetrics> messageSources = this.applicationContext
                .getBeansOfType(MessageSourceMetrics.class);
        for (Entry<String, MessageSourceMetrics> entry : messageSources.entrySet()) {
            // If the source is proxied, we have to extract the target to expose as an MBean.
            // The MetadataMBeanInfoAssembler does not support JDK dynamic proxies.
            MessageSourceMetrics monitor = (MessageSourceMetrics) extractTarget(entry.getValue());
            this.sources.add(monitor);
        }

        Map<String, MessageChannelMetrics> messageChannels = this.applicationContext
                .getBeansOfType(MessageChannelMetrics.class);
        for (Entry<String, MessageChannelMetrics> entry : messageChannels.entrySet()) {
            // If the channel is proxied, we have to extract the target to expose as an MBean.
            // The MetadataMBeanInfoAssembler does not support JDK dynamic proxies.
            MessageChannelMetrics monitor = (MessageChannelMetrics) extractTarget(entry.getValue());
            this.channels.add(monitor);
        }
        Map<String, MessageProducer> messageProducers = this.applicationContext
                .getBeansOfType(MessageProducer.class);
        for (Entry<String, MessageProducer> entry : messageProducers.entrySet()) {
            MessageProducer messageProducer = entry.getValue();
            if (messageProducer instanceof Lifecycle) {
                Lifecycle target = (Lifecycle) extractTarget(messageProducer);
                if (!(target instanceof AbstractMessageProducingHandler)) {
                    this.inboundLifecycleMessageProducers.add(target);
                }
            }
        }
        super.afterSingletonsInstantiated();
        try {
            for (Map.Entry<String, MessageChannelMetrics> entry : registerChannels(this.channels).entrySet()) {
                //super.registerBeanNameOrInstance(entry.getValue(), entry.getKey());
            }
            for (Map.Entry<String, MessageHandlerMetrics> entry : registerHandlers(this.handlers).entrySet()) {
                //super.registerBeanNameOrInstance(entry.getValue(), entry.getKey());
            }
            for (Map.Entry<String, MessageSourceMetrics> entry : registerSources(this.sources).entrySet()) {
                //super.registerBeanNameOrInstance(entry.getValue(), entry.getKey());
            }
            for (Map.Entry<String, AbstractEndpoint> entry : registerEndpoints().entrySet()) {
                //ObjectName objectName = registerBeanInstance(new ManagedEndpoint(entry.getValue()), entry.getKey());
                //logger.info("Registered endpoint without MessageSource: " + objectName);
            }

            if (this.applicationContext
                    .containsBean(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME)) {
                Object messageHistoryConfigurer = this.applicationContext
                        .getBean(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME);
                if (messageHistoryConfigurer instanceof MessageHistoryConfigurer) {
                    registerBeanInstance(messageHistoryConfigurer,
                            IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME);
                }
            }
        } catch (RuntimeException e) {
            unregisterBeans();
            throw e;
        }

    }

    private MessageHandler handlerInAnonymousWrapper(final Object bean) {
        if (bean != null && bean.getClass().isAnonymousClass()) {
            final AtomicReference<MessageHandler> wrapped = new AtomicReference<MessageHandler>();
            ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {

                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    field.setAccessible(true);
                    Object handler = field.get(bean);
                    if (handler instanceof MessageHandler) {
                        wrapped.set((MessageHandler) handler);
                    }
                }
            }, new FieldFilter() {

                @Override
                public boolean matches(Field field) {
                    return wrapped.get() == null && field.getName().startsWith("val$");
                }
            });
            return wrapped.get();
        } else {
            return null;
        }
    }

    /**
     * Copy of private method in super class. Needed so we can avoid using the bean factory to extract the bean again,
     * and risk it being a proxy (which it almost certainly is by now).
     *
     * @param bean the bean instance to register
     * @param beanKey the bean name or human readable version if autogenerated
     * @return the JMX object name of the MBean that was registered
     */
    private ObjectName registerBeanInstance(Object bean, String beanKey) {
        try {
            ObjectName objectName = getObjectName(bean, beanKey);
            Object mbeanToExpose = null;
            if (isMBean(bean.getClass())) {
                mbeanToExpose = bean;
            } else {
                DynamicMBean adaptedBean = adaptMBeanIfPossible(bean);
                if (adaptedBean != null) {
                    mbeanToExpose = adaptedBean;
                }
            }
            if (mbeanToExpose != null) {
                if (logger.isInfoEnabled()) {
                    logger.info("Located MBean '" + beanKey + "': registering with JMX server as MBean ["
                            + objectName + "]");
                }
                doRegister(mbeanToExpose, objectName);
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Located managed bean '" + beanKey + "': registering with JMX server as MBean ["
                            + objectName + "]");
                }
                ModelMBean mbean = createAndConfigureMBean(bean, beanKey);
                doRegister(mbean, objectName);
                // injectNotificationPublisherIfNecessary(bean, mbean, objectName);
            }
            return objectName;
        } catch (JMException e) {
            throw new UnableToRegisterMBeanException(
                    "Unable to register MBean [" + bean + "] with key '" + beanKey + "'", e);
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        channelsByName.clear();
        handlersByName.clear();
        sourcesByName.clear();
        for (MessageChannelMetrics monitor : channels) {
            logger.info("Summary on shutdown: " + monitor);
        }
        for (MessageHandlerMetrics monitor : handlers) {
            logger.info("Summary on shutdown: " + monitor);
        }
    }

    /**
     * Shutdown active components.
     *
     * @param howLong The time to wait in total for all activities to complete
     * in milliseconds.
     */
    public void stopActiveComponents(long howLong) {
        if (!this.shuttingDown.compareAndSet(false, true)) {
            logger.error("Shutdown already in process");
            return;
        }
        this.shutdownDeadline = System.currentTimeMillis() + howLong;
        try {
            logger.debug("Running shutdown");
            doShutdown();
        } catch (Exception e) {
            logger.error("Orderly shutdown failed", e);
        }
    }

    /**
     * Perform orderly shutdown - called or executed from
     * {@link #stopActiveComponents(long)}.
     */
    private void doShutdown() {
        try {
            orderlyShutdownCapableComponentsBefore();
            stopActiveChannels();
            stopMessageSources();
            stopInboundMessageProducers();
            // Wait any remaining time for messages to quiesce
            long timeLeft = shutdownDeadline - System.currentTimeMillis();
            if (timeLeft > 0) {
                try {
                    Thread.sleep(timeLeft);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    logger.error("Interrupted while waiting for quiesce");
                }
            }
            orderlyShutdownCapableComponentsAfter();
        } finally {
            shuttingDown.set(false);
        }
    }

    /**
     * Stops all message sources - may cause interrupts.
     */
    public void stopMessageSources() {
        for (Entry<String, MessageSourceMetrics> entry : this.allSourcesByName.entrySet()) {
            MessageSourceMetrics sourceMetrics = entry.getValue();
            if (sourceMetrics instanceof Lifecycle) {
                if (logger.isInfoEnabled()) {
                    logger.info("Stopping message source " + sourceMetrics);
                }
                ((Lifecycle) sourceMetrics).stop();
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Message source " + sourceMetrics + " cannot be stopped");
                }
            }
        }
    }

    /**
     * Stops all inbound message producers (that are not {@link OrderlyShutdownCapable})
     * - may cause interrupts.
     */
    public void stopInboundMessageProducers() {
        for (Lifecycle producer : this.inboundLifecycleMessageProducers) {
            if (!(producer instanceof OrderlyShutdownCapable)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Stopping message producer " + producer);
                }
                producer.stop();
            }
        }
    }

    public void stopActiveChannels() {
        // Stop any "active" channels (JMS etc).
        for (Entry<String, MessageChannelMetrics> entry : this.allChannelsByName.entrySet()) {
            MessageChannelMetrics metrics = entry.getValue();
            MessageChannel channel = (MessageChannel) metrics;
            if (channel instanceof Lifecycle) {
                if (logger.isInfoEnabled()) {
                    logger.info("Stopping channel " + channel);
                }
                ((Lifecycle) channel).stop();
            }
        }
    }

    protected final void orderlyShutdownCapableComponentsBefore() {
        logger.debug("Initiating stop OrderlyShutdownCapable components");
        Map<String, OrderlyShutdownCapable> components = this.applicationContext
                .getBeansOfType(OrderlyShutdownCapable.class);
        for (Entry<String, OrderlyShutdownCapable> componentEntry : components.entrySet()) {
            OrderlyShutdownCapable component = componentEntry.getValue();
            int n = component.beforeShutdown();
            if (logger.isInfoEnabled()) {
                logger.info(
                        "Initiated stop for component " + component + "; it reported " + n + " active messages");
            }
        }
        logger.debug("Initiated stop OrderlyShutdownCapable components");
    }

    protected final void orderlyShutdownCapableComponentsAfter() {
        logger.debug("Finalizing stop OrderlyShutdownCapable components");
        Map<String, OrderlyShutdownCapable> components = this.applicationContext
                .getBeansOfType(OrderlyShutdownCapable.class);
        for (Entry<String, OrderlyShutdownCapable> componentEntry : components.entrySet()) {
            OrderlyShutdownCapable component = componentEntry.getValue();
            int n = component.afterShutdown();
            if (logger.isInfoEnabled()) {
                logger.info(
                        "Finalized stop for component " + component + "; it reported " + n + " active messages");
            }
        }
        logger.debug("Finalized stop OrderlyShutdownCapable components");
    }

    public int getChannelCount() {
        return channelsByName.size();
    }

    public int getHandlerCount() {
        return handlersByName.size();
    }

    public String[] getHandlerNames() {
        return handlersByName.keySet().toArray(new String[handlersByName.size()]);
    }

    public int getActiveHandlerCount() {
        return (int) getActiveHandlerCountLong();
    }

    public long getActiveHandlerCountLong() {
        int count = 0;
        for (MessageHandlerMetrics monitor : handlers) {
            count += monitor.getActiveCountLong();
        }
        return count;
    }

    public int getQueuedMessageCount() {
        int count = 0;
        for (MessageChannelMetrics monitor : channels) {
            if (monitor instanceof QueueChannel) {
                count += ((QueueChannel) monitor).getQueueSize();
            }
        }
        return count;
    }

    public String[] getChannelNames() {
        return channelsByName.keySet().toArray(new String[channelsByName.size()]);
    }

    public Statistics getHandlerDuration(String name) {
        if (handlersByName.containsKey(name)) {
            return handlersByName.get(name).getDuration();
        }
        logger.debug("No handler found for (" + name + ")");
        return null;
    }

    public int getSourceMessageCount(String name) {
        return (int) getSourceMessageCountLong(name);
    }

    public long getSourceMessageCountLong(String name) {
        if (sourcesByName.containsKey(name)) {
            return sourcesByName.get(name).getMessageCountLong();
        }
        logger.debug("No source found for (" + name + ")");
        return -1;
    }

    public int getChannelReceiveCount(String name) {
        return (int) getChannelReceiveCountLong(name);
    }

    public long getChannelReceiveCountLong(String name) {
        if (channelsByName.containsKey(name)) {
            if (channelsByName.get(name) instanceof PollableChannelManagement) {
                return ((PollableChannelManagement) channelsByName.get(name)).getReceiveCountLong();
            }
        }
        logger.debug("No channel found for (" + name + ")");
        return -1;
    }

    public Statistics getChannelSendRate(String name) {
        if (channelsByName.containsKey(name)) {
            return channelsByName.get(name).getSendRate();
        }
        logger.debug("No channel found for (" + name + ")");
        return null;
    }

    public Statistics getChannelErrorRate(String name) {
        if (channelsByName.containsKey(name)) {
            return channelsByName.get(name).getErrorRate();
        }
        logger.debug("No channel found for (" + name + ")");
        return null;
    }

    @SuppressWarnings("unchecked")
    private Map<String, MessageChannelMetrics> registerChannels(Set<MessageChannelMetrics> messageChannelMetrics) {
        final Map<String, MessageChannelMetrics> metricsEnabledChannels = new HashMap<String, MessageChannelMetrics>();
        for (MessageChannelMetrics monitor : messageChannelMetrics) {
            String name = ((NamedComponent) monitor).getComponentName();
            this.allChannelsByName.put(name, monitor); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            if (!matches(this.componentNamePatterns, name)) { //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                continue;
            }
            // Only register once...
            if (!this.channelsByName.containsKey(name)) { //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                String beanKey = getChannelBeanKey(name);
                logger.info("Registering MessageChannel " + name);
                if (name != null) {
                    this.channelsByName.put(name, monitor);
                }
                AbstractMessageChannelMetrics metrics = this.metricsFactory.createChannelMetrics(name); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                Assert.state(metrics != null, "'metrics' must not be null");
                Boolean enabled = smartMatch(this.enabledCountsPatterns, name); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                if (enabled != null) {
                    monitor.enableCounts(enabled);
                }
                enabled = smartMatch(this.enabledStatsPatterns, name); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                if (enabled != null) {
                    monitor.enableStats(enabled);
                    metrics.setFullStatsEnabled(enabled);
                }
                if (monitor instanceof ConfigurableMetricsAware) {
                    ((ConfigurableMetricsAware<AbstractMessageChannelMetrics>) monitor).configureMetrics(metrics);
                }
                metricsEnabledChannels.put(beanKey, monitor);
            }
        }
        return metricsEnabledChannels;
    }

    @SuppressWarnings("unchecked")
    private Map<String, MessageHandlerMetrics> registerHandlers(Set<MessageHandlerMetrics> messageHandlerMetrics) {
        final Map<String, MessageHandlerMetrics> metricsEnabledHandlers = new HashMap<String, MessageHandlerMetrics>();
        for (MessageHandlerMetrics handler : messageHandlerMetrics) {
            MessageHandlerMetrics monitor = enhanceHandlerMonitor(handler); //<<<<<<<<<<<<<<<<<<<<<<<<<
            String name = monitor.getManagedName();
            this.allHandlersByName.put(name, monitor); //<<<<<<<<<<<<<<<<<<<<<<<<<
            if (!matches(this.componentNamePatterns, name)) { //<<<<<<<<<<<<<<<<<<<<<<<<<
                continue;
            }
            // Only register once...
            if (!handlersByName.containsKey(name)) { //<<<<<<<<<<<<<<<<<<<<<<<<<
                String beanKey = getHandlerBeanKey(monitor); //<<<<<<<<<<<<<<<<<<<<<<<<<
                if (name != null) {
                    handlersByName.put(name, monitor); //<<<<<<<<<<<<<<<<<<<<<<<<<
                }
                AbstractMessageHandlerMetrics metrics = this.metricsFactory.createHandlerMetrics(name); //<<<<<<<<<<<<<<<<<<<<<<<<<
                Assert.state(metrics != null, "'metrics' must not be null");
                Boolean enabled = smartMatch(this.enabledCountsPatterns, name); //<<<<<<<<<<<<<<<<<<<<<<<<<
                if (enabled != null) {
                    monitor.enableCounts(enabled);
                }
                enabled = smartMatch(this.enabledStatsPatterns, name);
                if (enabled != null) {
                    monitor.enableStats(enabled);
                    metrics.setFullStatsEnabled(enabled);
                }
                if (monitor instanceof ConfigurableMetricsAware) {
                    ((ConfigurableMetricsAware<AbstractMessageHandlerMetrics>) monitor).configureMetrics(metrics);
                }
                metricsEnabledHandlers.put(beanKey, monitor);
            }
        }
        return metricsEnabledHandlers;
    }

    private Map<String, MessageSourceMetrics> registerSources(Set<MessageSourceMetrics> messageSourceMetrics) {
        final Map<String, MessageSourceMetrics> metricsEnabledSources = new HashMap<String, MessageSourceMetrics>();
        for (MessageSourceMetrics source : messageSourceMetrics) {
            MessageSourceMetrics monitor = enhanceSourceMonitor(source);
            String name = monitor.getManagedName();
            this.allSourcesByName.put(name, monitor);
            if (!matches(this.componentNamePatterns, name)) {
                continue;
            }
            // Only register once...
            if (!sourcesByName.containsKey(name)) {
                String beanKey = getSourceBeanKey(monitor);
                if (name != null) {
                    sourcesByName.put(name, monitor);
                }
                Boolean enabled = smartMatch(this.enabledCountsPatterns, name);
                if (enabled != null) {
                    monitor.enableCounts(enabled);
                }
                metricsEnabledSources.put(beanKey, monitor);
            }
        }
        return metricsEnabledSources;
    }

    private Map<String, AbstractEndpoint> registerEndpoints() {
        final Map<String, AbstractEndpoint> metricsEnabledEndpoints = new HashMap<String, AbstractEndpoint>();
        String[] names = this.applicationContext.getBeanNamesForType(AbstractEndpoint.class);
        Set<String> endpointNames = new HashSet<String>();
        for (String name : names) {
            if (!beansByEndpointName.values().contains(name)) {
                AbstractEndpoint endpoint = this.applicationContext.getBean(name, AbstractEndpoint.class);
                String beanKey;
                name = endpoint.getComponentName();
                String source;
                if (name.startsWith("_org.springframework.integration")) {
                    name = getInternalComponentName(name);
                    source = "internal";
                } else {
                    name = endpoint.getComponentName();
                    source = "endpoint";
                }
                if (!matches(this.componentNamePatterns, name)) {
                    continue;
                }
                if (endpointNames.contains(name)) {
                    int count = 0;
                    String unique = name + "#" + count;
                    while (endpointNames.contains(unique)) {
                        unique = name + "#" + (++count);
                    }
                    name = unique;
                }
                endpointNames.add(name);
                beanKey = getEndpointBeanKey(endpoint, name, source);
                metricsEnabledEndpoints.put(beanKey, endpoint);
            }
        }
        return metricsEnabledEndpoints;
    }

    /**
     * Simple pattern match against the supplied patterns; also supports negated ('!')
     * patterns. First match wins (positive or negative).
     * @param patterns the patterns.
     * @param name the name to match.
     * @return true if positive match, false if no match or negative match.
     */
    private boolean matches(String[] patterns, String name) {
        Boolean match = smartMatch(patterns, name);
        return match == null ? false : match;
    }

    /**
     * Simple pattern match against the supplied patterns; also supports negated ('!')
     * patterns. First match wins (positive or negative).
     * @param patterns the patterns.
     * @param name the name to match.
     * @return null if no match; true for positive match; false for negative match.
     */
    private Boolean smartMatch(String[] patterns, String name) {
        if (patterns != null) {
            for (String pattern : patterns) {
                boolean reverse = false;
                String patternToUse = pattern;
                if (pattern.startsWith("!")) {
                    reverse = true;
                    patternToUse = pattern.substring(1);
                } else if (pattern.startsWith("\\")) {
                    patternToUse = pattern.substring(1);
                }
                if (PatternMatchUtils.simpleMatch(patternToUse, name)) {
                    return !reverse;
                }
            }
        }
        return null;
    }

    private Object extractTarget(Object bean) {
        if (!(bean instanceof Advised)) {
            return bean;
        }
        Advised advised = (Advised) bean;
        if (advised.getTargetSource() == null) {
            return null;
        }
        try {
            return extractTarget(advised.getTargetSource().getTarget());
        } catch (Exception e) {
            logger.error("Could not extract target", e);
            return null;
        }
    }

    private String getChannelBeanKey(String channel) {
        String name = "" + channel;
        if (name.startsWith("org.springframework.integration")) {
            name = name + ",source=anonymous";
        }
        return String.format(domain + ":type=MessageChannel,name=%s" + getStaticNames(), name);
    }

    private String getHandlerBeanKey(MessageHandlerMetrics handler) {
        // This ordering of keys seems to work with default settings of JConsole
        return String.format(domain + ":type=MessageHandler,name=%s,bean=%s" + getStaticNames(),
                handler.getManagedName(), handler.getManagedType());
    }

    private String getSourceBeanKey(MessageSourceMetrics source) {
        // This ordering of keys seems to work with default settings of JConsole
        return String.format(domain + ":type=MessageSource,name=%s,bean=%s" + getStaticNames(),
                source.getManagedName(), source.getManagedType());
    }

    private String getEndpointBeanKey(AbstractEndpoint endpoint, String name, String source) {
        // This ordering of keys seems to work with default settings of JConsole
        return String.format(domain + ":type=ManagedEndpoint,name=%s,bean=%s" + getStaticNames(), name, source);
    }

    private String getStaticNames() {
        if (objectNameStaticProperties.isEmpty()) {
            return "";
        }
        StringBuilder builder = new StringBuilder();

        for (Object key : objectNameStaticProperties.keySet()) {
            builder.append("," + key + "=" + objectNameStaticProperties.get(key));
        }
        return builder.toString();
    }

    private MessageHandlerMetrics enhanceHandlerMonitor(MessageHandlerMetrics monitor) {

        MessageHandlerMetrics result = monitor;

        if (monitor.getManagedName() != null && monitor.getManagedType() != null) {
            return monitor;
        }

        // Assignment algorithm and bean id, with bean id pulled reflectively out of enclosing endpoint if possible
        String[] names = this.applicationContext.getBeanNamesForType(AbstractEndpoint.class);

        String name = null;
        String endpointName = null;
        String source = "endpoint";
        Object endpoint = null;

        for (String beanName : names) {
            endpoint = this.applicationContext.getBean(beanName);
            try {
                Object field = extractTarget(getField(endpoint, "handler"));
                if (field == monitor || this.extractTarget(this.handlerInAnonymousWrapper(field)) == monitor) {
                    name = beanName;
                    endpointName = beanName;
                    break;
                }
            } catch (Exception e) {
                logger.trace("Could not get handler from bean = " + beanName);
            }
        }
        if (name != null && endpoint != null && name.startsWith("_org.springframework.integration")) {
            name = getInternalComponentName(name);
            source = "internal";
        }
        if (name != null && endpoint != null && name.startsWith("org.springframework.integration")) {
            Object target = endpoint;
            if (endpoint instanceof Advised) {
                TargetSource targetSource = ((Advised) endpoint).getTargetSource();
                if (targetSource != null) {
                    try {
                        target = targetSource.getTarget();
                    } catch (Exception e) {
                        logger.debug("Could not get handler from bean = " + name);
                    }
                }
            }
            Object field = getField(target, "inputChannel");
            if (field != null) {
                if (!anonymousHandlerCounters.containsKey(field)) {
                    anonymousHandlerCounters.put(field, new AtomicLong());
                }
                AtomicLong count = anonymousHandlerCounters.get(field);
                long total = count.incrementAndGet();
                String suffix = "";
                /*
                 * Short hack to makes sure object names are unique if more than one endpoint has the same input channel
                 */
                if (total > 1) {
                    suffix = "#" + total;
                }
                name = field + suffix;
                source = "anonymous";
            }
        }

        if (endpoint instanceof Lifecycle) {
            // Wrap the monitor in a lifecycle so it exposes the start/stop operations
            if (monitor instanceof MappingMessageRouterManagement) {
                if (monitor instanceof TrackableComponent) {
                    result = new TrackableRouterMetrics((Lifecycle) endpoint,
                            (MappingMessageRouterManagement) monitor);
                } else {
                    result = new RouterMetrics((Lifecycle) endpoint, (MappingMessageRouterManagement) monitor);
                }
            } else {
                if (monitor instanceof TrackableComponent) {
                    result = new LifecycleTrackableMessageHandlerMetrics((Lifecycle) endpoint, monitor);
                } else {
                    result = new LifecycleMessageHandlerMetrics((Lifecycle) endpoint, monitor);
                }
            }
        }

        if (name == null) {
            if (monitor instanceof NamedComponent) {
                name = ((NamedComponent) monitor).getComponentName();
            }
            if (name == null) {
                name = monitor.toString();
            }
            source = "handler";
        }

        if (endpointName != null) {
            beansByEndpointName.put(name, endpointName);
        }

        monitor.setManagedType(source);
        monitor.setManagedName(name);

        return result;

    }

    private String getInternalComponentName(String name) {
        return name.substring("_org.springframework.integration".length() + 1);
    }

    private MessageSourceMetrics enhanceSourceMonitor(MessageSourceMetrics monitor) {

        MessageSourceMetrics result = monitor;

        if (monitor.getManagedName() != null) {
            return monitor;
        }

        // Assignment algorithm and bean id, with bean id pulled reflectively out of enclosing endpoint if possible
        String[] names = this.applicationContext.getBeanNamesForType(AbstractEndpoint.class);

        String name = null;
        String endpointName = null;
        String source = "endpoint";
        Object endpoint = null;

        for (String beanName : names) {
            endpoint = this.applicationContext.getBean(beanName);
            Object field = null;
            try {
                field = extractTarget(getField(endpoint, "source"));
            } catch (Exception e) {
                logger.trace("Could not get source from bean = " + beanName);
            }
            if (field == monitor) {
                name = beanName;
                endpointName = beanName;
                break;
            }
        }
        if (name != null && endpoint != null && name.startsWith("_org.springframework.integration")) {
            name = getInternalComponentName(name);
            source = "internal";
        }
        if (name != null && endpoint != null && name.startsWith("org.springframework.integration")) {
            Object target = endpoint;
            if (endpoint instanceof Advised) {
                TargetSource targetSource = ((Advised) endpoint).getTargetSource();
                if (targetSource != null) {
                    try {
                        target = targetSource.getTarget();
                    } catch (Exception e) {
                        logger.debug("Could not get handler from bean = " + name);
                    }
                }
            }
            Object field = getField(target, "outputChannel");
            if (field != null) {
                if (!anonymousSourceCounters.containsKey(field)) {
                    anonymousSourceCounters.put(field, new AtomicLong());
                }
                AtomicLong count = anonymousSourceCounters.get(field);
                long total = count.incrementAndGet();
                String suffix = "";
                /*
                 * Short hack to makes sure object names are unique if more than one endpoint has the same input channel
                 */
                if (total > 1) {
                    suffix = "#" + total;
                }
                name = field + suffix;
                source = "anonymous";
            }
        }

        if (endpoint instanceof Lifecycle) {
            // Wrap the monitor in a lifecycle so it exposes the start/stop operations
            if (endpoint instanceof TrackableComponent) {
                result = new LifecycleTrackableMessageSourceMetrics((Lifecycle) endpoint, monitor);
            } else {
                result = new LifecycleMessageSourceMetrics((Lifecycle) endpoint, monitor);
            }
        }

        if (name == null) {
            name = monitor.toString();
            source = "handler";
        }

        if (endpointName != null) {
            beansByEndpointName.put(name, endpointName);
        }

        monitor.setManagedType(source);
        monitor.setManagedName(name);

        return result;
    }

    private static Object getField(Object target, String name) {
        Assert.notNull(target, "Target object must not be null");
        Field field = ReflectionUtils.findField(target.getClass(), name);
        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + name + "] on target [" + target + "]");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Getting field [" + name + "] from target [" + target + "]");
        }
        ReflectionUtils.makeAccessible(field);
        return ReflectionUtils.getField(field, target);
    }

    private class IntegrationJmxAttributeSource extends AnnotationJmxAttributeSource {

        @Override
        public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass)
                throws InvalidMetadataException {
            IntegrationManagedResource ann = AnnotationUtils.getAnnotation(beanClass,
                    IntegrationManagedResource.class);
            if (ann == null) {
                return null;
            }
            org.springframework.jmx.export.metadata.ManagedResource managedResource = new org.springframework.jmx.export.metadata.ManagedResource();
            AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource,
                    MetricsActivator.this.embeddedValueResolver);
            if (!"".equals(ann.value()) && !StringUtils.hasLength(managedResource.getObjectName())) {
                String value = ann.value();
                if (MetricsActivator.this.embeddedValueResolver != null) {
                    value = MetricsActivator.this.embeddedValueResolver.resolveStringValue(value);
                }
                managedResource.setObjectName(value);
            }
            return managedResource;
        }

    }

}