/*
* Copyright 2015-2016 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.support.management;
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.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
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
* @since 4.2
*
*/
public class IntegrationManagementConfigurer implements SmartInitializingSingleton, ApplicationContextAware,
BeanNameAware {
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<String, MessageChannelMetrics>();
private final Map<String, MessageHandlerMetrics> handlersByName = new HashMap<String, MessageHandlerMetrics>();
private final Map<String, MessageSourceMetrics> sourcesByName = new HashMap<String, MessageSourceMetrics>();
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 = { };
@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 (this.metricsFactory == null && StringUtils.hasText(this.metricsFactoryBeanName)) {
this.metricsFactory = this.applicationContext.getBean(this.metricsFactoryBeanName, MetricsFactory.class);
}
if (this.metricsFactory == null) {
this.metricsFactory = new DefaultMetricsFactory();
}
Map<String, IntegrationManagement> managed = this.applicationContext.getBeansOfType(IntegrationManagement.class);
for (Entry<String, IntegrationManagement> entry : managed.entrySet()) {
IntegrationManagement bean = entry.getValue();
bean.setLoggingEnabled(this.defaultLoggingEnabled);
if (bean instanceof MessageChannelMetrics) {
configureChannelMetrics(entry.getKey(), (MessageChannelMetrics) bean);
}
else if (bean instanceof MessageHandlerMetrics) {
configureHandlerMetrics(entry.getKey(), (MessageHandlerMetrics) bean);
}
else if (bean instanceof MessageSourceMetrics) {
configureSourceMetrics(entry.getKey(), (MessageSourceMetrics) bean);
}
}
}
@SuppressWarnings("unchecked")
private void configureChannelMetrics(String name, MessageChannelMetrics bean) {
AbstractMessageChannelMetrics metrics = this.metricsFactory.createChannelMetrics(name);
Assert.state(metrics != null, "'metrics' must not be null");
Boolean enabled = smartMatch(this.enabledCountsPatterns, name);
if (enabled != null) {
bean.setCountsEnabled(enabled);
}
else {
bean.setCountsEnabled(this.defaultCountsEnabled);
}
enabled = smartMatch(this.enabledStatsPatterns, name);
if (enabled != null) {
bean.setStatsEnabled(enabled);
metrics.setFullStatsEnabled(enabled);
}
else {
bean.setStatsEnabled(this.defaultStatsEnabled);
metrics.setFullStatsEnabled(this.defaultStatsEnabled);
}
if (bean instanceof ConfigurableMetricsAware) {
((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");
Boolean enabled = smartMatch(this.enabledCountsPatterns, name);
if (enabled != null) {
bean.setCountsEnabled(enabled);
}
else {
bean.setCountsEnabled(this.defaultCountsEnabled);
}
enabled = smartMatch(this.enabledStatsPatterns, name);
if (enabled != null) {
bean.setStatsEnabled(enabled);
metrics.setFullStatsEnabled(enabled);
}
else {
bean.setStatsEnabled(this.defaultStatsEnabled);
metrics.setFullStatsEnabled(this.defaultStatsEnabled);
}
if (bean instanceof ConfigurableMetricsAware) {
((ConfigurableMetricsAware<AbstractMessageHandlerMetrics>) bean).configureMetrics(metrics);
}
this.handlersByName.put(bean.getManagedName() != null ? bean.getManagedName() : name, bean);
}
private void configureSourceMetrics(String name, MessageSourceMetrics bean) {
Boolean enabled = smartMatch(this.enabledCountsPatterns, name);
if (enabled != null) {
bean.setCountsEnabled(enabled);
}
else {
bean.setCountsEnabled(this.defaultCountsEnabled);
}
this.sourcesByName.put(bean.getManagedName() != null ? bean.getManagedName() : name, bean);
}
/**
* 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; //NOSONAR - intentional null return
}
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;
}
}