org.springframework.integration.config.IntegrationManagementConfigurer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.config.IntegrationManagementConfigurer.java

Source

/*
 * Copyright 2015-2018 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.
 */

package org.springframework.integration.config;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.management.AbstractMessageChannelMetrics;
import org.springframework.integration.support.management.AbstractMessageHandlerMetrics;
import org.springframework.integration.support.management.ConfigurableMetricsAware;
import org.springframework.integration.support.management.DefaultMetricsFactory;
import org.springframework.integration.support.management.IntegrationManagement;
import org.springframework.integration.support.management.IntegrationManagement.ManagementOverrides;
import org.springframework.integration.support.management.MessageChannelMetrics;
import org.springframework.integration.support.management.MessageHandlerMetrics;
import org.springframework.integration.support.management.MessageSourceMetrics;
import org.springframework.integration.support.management.MessageSourceMetricsConfigurer;
import org.springframework.integration.support.management.MetricsFactory;
import org.springframework.integration.support.management.PollableChannelManagement;
import org.springframework.integration.support.management.metrics.MetricsCaptor;
import org.springframework.integration.support.management.micrometer.MicrometerMetricsCaptor;
import org.springframework.integration.support.utils.PatternMatchUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * Configures beans that implement {@link IntegrationManagement}.
 * Configures counts, stats, logging for all (or selected) components.
 *
 * @author Gary Russell
 * @author Artem Bilan
 * @author Meherzad Lahewala
 *
 * @since 4.2
 *
 */
public class IntegrationManagementConfigurer implements SmartInitializingSingleton, ApplicationContextAware,
        BeanNameAware, DestructionAwareBeanPostProcessor {

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

    public static final String MANAGEMENT_CONFIGURER_NAME = "integrationManagementConfigurer";

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

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

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

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

    private ApplicationContext applicationContext;

    private String beanName;

    private boolean defaultLoggingEnabled = true;

    private Boolean defaultCountsEnabled = false;

    private Boolean defaultStatsEnabled = false;

    private MetricsFactory metricsFactory;

    private String metricsFactoryBeanName;

    private String[] enabledCountsPatterns = {};

    private String[] enabledStatsPatterns = {};

    private volatile boolean singletonsInstantiated;

    private MetricsCaptor metricsCaptor;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    /**
     * Set a metrics factory.
     * Has a precedence over {@link #metricsFactoryBeanName}.
     * Defaults to {@link DefaultMetricsFactory}.
     * @param metricsFactory the factory.
     * @since 4.2
     */
    public void setMetricsFactory(MetricsFactory metricsFactory) {
        this.metricsFactory = metricsFactory;
    }

    /**
     * Set a metrics factory bean name.
     * Is used if {@link #metricsFactory} isn't specified.
     * @param metricsFactory the factory.
     * @since 4.2
     */
    public void setMetricsFactoryBeanName(String metricsFactory) {
        this.metricsFactoryBeanName = metricsFactory;
    }

    /**
     * Set the array of simple patterns for component names for which message counts will
     * be enabled (defaults to '*').
     * 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.
     */
    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 '*').
     * 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.
     */
    public void setEnabledStatsPatterns(String[] enabledStatsPatterns) {
        Assert.notEmpty(enabledStatsPatterns, "enabledStatsPatterns must not be empty");
        this.enabledStatsPatterns = Arrays.copyOf(enabledStatsPatterns, enabledStatsPatterns.length);
    }

    /**
     * Set whether managed components maintain message counts by default.
     * Defaults to false, unless an Integration MBean Exporter is configured.
     * @param defaultCountsEnabled true to enable.
     */
    public void setDefaultCountsEnabled(Boolean defaultCountsEnabled) {
        this.defaultCountsEnabled = defaultCountsEnabled;
    }

    public Boolean getDefaultCountsEnabled() {
        return this.defaultCountsEnabled;
    }

    /**
     * Set whether managed components maintain message statistics by default.
     * Defaults to false, unless an Integration MBean Exporter is configured.
     * @param defaultStatsEnabled true to enable.
     */
    public void setDefaultStatsEnabled(Boolean defaultStatsEnabled) {
        this.defaultStatsEnabled = defaultStatsEnabled;
    }

    public Boolean getDefaultStatsEnabled() {
        return this.defaultStatsEnabled;
    }

    /**
     * Disable all logging in the normal message flow in framework components. When 'false', such logging will be
     * skipped, regardless of logging level. When 'true', the logging is controlled as normal by the logging
     * subsystem log level configuration.
     * <p>
     * Exception logging (debug or otherwise) is not affected by this setting.
     * <p>
     * It has been found that in high-volume messaging environments, calls to methods such as
     * {@code logger.isDebuggingEnabled()} can be quite expensive and account for an inordinate amount of CPU
     * time.
     * <p>
     * Set this to false to disable logging by default in all framework components that implement
     * {@link IntegrationManagement} (channels, message handlers etc). This turns off logging such as
     * "PreSend on channel", "Received message" etc.
     * <p>
     * After the context is initialized, individual components can have their setting changed by invoking
     * {@link IntegrationManagement#setLoggingEnabled(boolean)}.
     * @param defaultLoggingEnabled defaults to true.
     */
    public void setDefaultLoggingEnabled(boolean defaultLoggingEnabled) {
        this.defaultLoggingEnabled = defaultLoggingEnabled;
    }

    @Override
    public void afterSingletonsInstantiated() {
        Assert.state(this.applicationContext != null, "'applicationContext' must not be null");
        Assert.state(MANAGEMENT_CONFIGURER_NAME.equals(this.beanName),
                getClass().getSimpleName() + " bean name must be " + MANAGEMENT_CONFIGURER_NAME);
        if (ClassUtils.isPresent("io.micrometer.core.instrument.MeterRegistry",
                this.applicationContext.getClassLoader())) {
            this.metricsCaptor = MicrometerMetricsCaptor.loadCaptor(this.applicationContext);
        }
        if (this.metricsCaptor != null) {
            injectCaptor();
            registerComponentGauges();
        }
        if (this.metricsFactory == null && StringUtils.hasText(this.metricsFactoryBeanName)) {
            this.metricsFactory = this.applicationContext.getBean(this.metricsFactoryBeanName,
                    MetricsFactory.class);
        }
        if (this.metricsFactory == null) {
            Map<String, MetricsFactory> factories = this.applicationContext.getBeansOfType(MetricsFactory.class);
            if (factories.size() == 1) {
                this.metricsFactory = factories.values().iterator().next();
            }
        }
        if (this.metricsFactory == null) {
            this.metricsFactory = new DefaultMetricsFactory();
        }
        this.sourceConfigurers.putAll(this.applicationContext.getBeansOfType(MessageSourceMetricsConfigurer.class));
        Map<String, IntegrationManagement> managed = this.applicationContext
                .getBeansOfType(IntegrationManagement.class);
        for (Entry<String, IntegrationManagement> entry : managed.entrySet()) {
            IntegrationManagement bean = entry.getValue();
            if (!bean.getOverrides().loggingConfigured) {
                bean.setLoggingEnabled(this.defaultLoggingEnabled);
            }
            String name = entry.getKey();
            doConfigureMetrics(bean, name);
        }
        this.singletonsInstantiated = true;
    }

    private void injectCaptor() {
        Map<String, IntegrationManagement> managed = this.applicationContext
                .getBeansOfType(IntegrationManagement.class);
        for (Entry<String, IntegrationManagement> entry : managed.entrySet()) {
            IntegrationManagement bean = entry.getValue();
            if (!bean.getOverrides().loggingConfigured) {
                bean.setLoggingEnabled(this.defaultLoggingEnabled);
            }
            bean.registerMetricsCaptor(this.metricsCaptor);
        }
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (this.singletonsInstantiated) {
            if (bean instanceof IntegrationManagement) {
                ((IntegrationManagement) bean).registerMetricsCaptor(this.metricsCaptor);
            }
            return doConfigureMetrics(bean, beanName);
        }
        return bean;
    }

    @Override
    public boolean requiresDestruction(Object bean) {
        return bean instanceof MessageChannelMetrics || bean instanceof MessageHandlerMetrics
                || bean instanceof MessageSourceMetrics;
    }

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (bean instanceof MessageChannelMetrics) {
            this.channelsByName.remove(beanName);
        } else if (bean instanceof MessageHandlerMetrics) {
            if (this.handlersByName.remove(beanName) == null) {
                this.handlersByName.remove(beanName + ".handler");
            }
        } else if (bean instanceof MessageSourceMetrics) {
            if (this.sourcesByName.remove(beanName) == null) {
                this.sourcesByName.remove(beanName + ".source");
            }
        }
    }

    private Object doConfigureMetrics(Object bean, String name) {
        if (bean instanceof MessageChannelMetrics) {
            configureChannelMetrics(name, (MessageChannelMetrics) bean);
        } else if (bean instanceof MessageHandlerMetrics) {
            configureHandlerMetrics(name, (MessageHandlerMetrics) bean);
        } else if (bean instanceof MessageSourceMetrics) {
            configureSourceMetrics(name, (MessageSourceMetrics) bean);
            this.sourceConfigurers.values().forEach(c -> c.configure((MessageSourceMetrics) bean, name));
        }
        return bean;
    }

    @SuppressWarnings("unchecked")
    private void configureChannelMetrics(String name, MessageChannelMetrics bean) {
        AbstractMessageChannelMetrics metrics;
        if (bean instanceof PollableChannelManagement) {
            metrics = this.metricsFactory.createPollableChannelMetrics(name);
        } else {
            metrics = this.metricsFactory.createChannelMetrics(name);
        }
        Assert.state(metrics != null, "'metrics' must not be null");
        ManagementOverrides overrides = bean.getOverrides();
        Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns);
        if (enabled != null) {
            bean.setCountsEnabled(enabled);
        } else {
            if (!overrides.countsConfigured) {
                bean.setCountsEnabled(this.defaultCountsEnabled);
            }
        }
        enabled = PatternMatchUtils.smartMatch(name, this.enabledStatsPatterns);
        if (enabled != null) {
            bean.setStatsEnabled(enabled);
            metrics.setFullStatsEnabled(enabled);
        } else {
            if (!overrides.statsConfigured) {
                bean.setStatsEnabled(this.defaultStatsEnabled);
                metrics.setFullStatsEnabled(this.defaultStatsEnabled);
            }
        }
        if (bean instanceof ConfigurableMetricsAware && !overrides.metricsConfigured) {
            ((ConfigurableMetricsAware<AbstractMessageChannelMetrics>) bean).configureMetrics(metrics);
        }
        this.channelsByName.put(name, bean);
    }

    @SuppressWarnings("unchecked")
    private void configureHandlerMetrics(String name, MessageHandlerMetrics bean) {
        AbstractMessageHandlerMetrics metrics = this.metricsFactory.createHandlerMetrics(name);
        Assert.state(metrics != null, "'metrics' must not be null");
        ManagementOverrides overrides = bean.getOverrides();
        Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns);
        if (enabled != null) {
            bean.setCountsEnabled(enabled);
        } else {
            if (!overrides.countsConfigured) {
                bean.setCountsEnabled(this.defaultCountsEnabled);
            }
        }
        enabled = PatternMatchUtils.smartMatch(name, this.enabledStatsPatterns);
        if (enabled != null) {
            bean.setStatsEnabled(enabled);
            metrics.setFullStatsEnabled(enabled);
        } else {
            if (!overrides.statsConfigured) {
                bean.setStatsEnabled(this.defaultStatsEnabled);
                metrics.setFullStatsEnabled(this.defaultStatsEnabled);
            }
        }
        if (bean instanceof ConfigurableMetricsAware && !overrides.metricsConfigured) {
            ((ConfigurableMetricsAware<AbstractMessageHandlerMetrics>) bean).configureMetrics(metrics);
        }

        this.handlersByName.put(bean.getManagedName() != null ? bean.getManagedName() : name, bean);
    }

    private void configureSourceMetrics(String name, MessageSourceMetrics bean) {
        Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns);
        if (enabled != null) {
            bean.setCountsEnabled(enabled);
        } else {
            if (!bean.getOverrides().countsConfigured) {
                bean.setCountsEnabled(this.defaultCountsEnabled);
            }
        }
        this.sourcesByName.put(bean.getManagedName() != null ? bean.getManagedName() : name, bean);
    }

    private void registerComponentGauges() {
        this.metricsCaptor
                .gaugeBuilder("spring.integration.channels", this,
                        (c) -> this.applicationContext.getBeansOfType(MessageChannel.class).size())
                .description("The number of message channels").build();

        this.metricsCaptor
                .gaugeBuilder("spring.integration.handlers", this,
                        (c) -> this.applicationContext.getBeansOfType(MessageHandler.class).size())
                .description("The number of message handlers").build();

        this.metricsCaptor
                .gaugeBuilder("spring.integration.sources", this,
                        (c) -> this.applicationContext.getBeansOfType(MessageSource.class).size())
                .description("The number of message sources").build();
    }

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

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

    public String[] getSourceNames() {
        return this.sourcesByName.keySet().toArray(new String[this.sourcesByName.size()]);
    }

    public MessageChannelMetrics getChannelMetrics(String name) {
        if (this.channelsByName.containsKey(name)) {
            return this.channelsByName.get(name);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("No channel found for (" + name + ")");
        }
        return null;
    }

    public MessageHandlerMetrics getHandlerMetrics(String name) {
        if (this.handlersByName.containsKey(name)) {
            return this.handlersByName.get(name);
        }
        if (this.handlersByName.containsKey(name + ".handler")) {
            return this.handlersByName.get(name + ".handler");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("No handler found for (" + name + ")");
        }
        return null;
    }

    public MessageSourceMetrics getSourceMetrics(String name) {
        if (this.sourcesByName.containsKey(name)) {
            return this.sourcesByName.get(name);
        }
        if (this.sourcesByName.containsKey(name + ".source")) {
            return this.sourcesByName.get(name + ".source");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("No source found for (" + name + ")");
        }
        return null;
    }

}