/*
* Copyright 2002-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.monitor;
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.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.QueueChannel;
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.MessageSourceManagement;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.history.MessageHistoryConfigurer;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.integration.support.management.IntegrationManagementConfigurer;
import org.springframework.integration.support.management.LifecycleMessageHandlerMetrics;
import org.springframework.integration.support.management.LifecycleMessageSourceManagement;
import org.springframework.integration.support.management.LifecycleMessageSourceMetrics;
import org.springframework.integration.support.management.LifecycleTrackableMessageHandlerMetrics;
import org.springframework.integration.support.management.LifecycleTrackableMessageSourceManagement;
import org.springframework.integration.support.management.LifecycleTrackableMessageSourceMetrics;
import org.springframework.integration.support.management.MappingMessageRouterManagement;
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.PollableChannelManagement;
import org.springframework.integration.support.management.RouterMetrics;
import org.springframework.integration.support.management.Statistics;
import org.springframework.integration.support.management.TrackableComponent;
import org.springframework.integration.support.management.TrackableRouterMetrics;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.UnableToRegisterMBeanException;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.MetricType;
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.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><context:mbean-export/></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
*/
@org.springframework.jmx.export.annotation.ManagedResource
public class IntegrationMBeanExporter extends MBeanExporter implements ApplicationContextAware,
EmbeddedValueResolverAware {
private static final Log logger = LogFactory.getLog(IntegrationMBeanExporter.class);
public static final String DEFAULT_DOMAIN = "org.springframework.integration";
private final IntegrationJmxAttributeSource 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> 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 MetadataNamingStrategy defaultNamingStrategy = new IntegrationMetadataNamingStrategy(this.attributeSource);
private String[] componentNamePatterns = { "*" };
private IntegrationManagementConfigurer managementConfigurer;
private volatile long shutdownDeadline;
private final AtomicBoolean shuttingDown = new AtomicBoolean();
public IntegrationMBeanExporter() {
super();
// Shouldn't be necessary, but to be on the safe side...
setAutodetect(false);
setNamingStrategy(this.defaultNamingStrategy);
setAssembler(new IntegrationMetadataMBeanInfoAssembler(this.attributeSource));
}
/**
* 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);
}
@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.attributeSource.setValueResolver(resolver);
}
@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 {
registerChannels();
registerHandlers();
registerSources();
registerEndpoints();
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);
}
}
if (!this.applicationContext.containsBean(IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME)) {
this.managementConfigurer = new IntegrationManagementConfigurer();
this.managementConfigurer.setDefaultCountsEnabled(true);
this.managementConfigurer.setDefaultStatsEnabled(true);
this.managementConfigurer.setApplicationContext(this.applicationContext);
this.managementConfigurer.setBeanName(IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME);
this.managementConfigurer.afterSingletonsInstantiated();
}
else {
this.managementConfigurer =
this.applicationContext.getBean(IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME,
IntegrationManagementConfigurer.class);
}
}
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(), field -> {
field.setAccessible(true);
Object handler = field.get(bean);
if (handler instanceof MessageHandler) {
wrapped.set((MessageHandler) handler);
}
}, field -> 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();
for (MessageChannelMetrics monitor : this.channels) {
logger.info("Summary on shutdown: " + monitor);
}
for (MessageHandlerMetrics monitor : this.handlers) {
logger.info("Summary on shutdown: " + monitor);
}
for (MessageSourceMetrics monitor : this.sources) {
logger.info("Summary on shutdown: " + monitor);
}
}
/**
* Shutdown active components.
*
* @param howLong The time to wait in total for all activities to complete
* in milliseconds.
*/
@ManagedOperation
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 = this.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 {
this.shuttingDown.set(false);
}
}
/**
* Stops all message sources - may cause interrupts.
*/
@ManagedOperation
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.
*/
@ManagedOperation
public void stopInboundMessageProducers() {
for (Lifecycle producer : this.inboundLifecycleMessageProducers) {
if (!(producer instanceof OrderlyShutdownCapable)) {
if (logger.isInfoEnabled()) {
logger.info("Stopping message producer " + producer);
}
producer.stop();
}
}
}
@ManagedOperation
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");
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Count")
public int getChannelCount() {
return this.managementConfigurer.getChannelNames().length;
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageHandler Count")
public int getHandlerCount() {
return this.managementConfigurer.getHandlerNames().length;
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageSource Count")
public int getSourceCount() {
return this.managementConfigurer.getSourceNames().length;
}
@ManagedAttribute
public String[] getHandlerNames() {
return this.managementConfigurer.getHandlerNames();
}
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Active Handler Count")
public int getActiveHandlerCount() {
return (int) getActiveHandlerCountLong();
}
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Active Handler Count")
public long getActiveHandlerCountLong() {
int count = 0;
for (MessageHandlerMetrics monitor : this.handlers) {
count += monitor.getActiveCountLong();
}
return count;
}
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Queued Message Count")
public int getQueuedMessageCount() {
int count = 0;
for (MessageChannelMetrics monitor : this.channels) {
if (monitor instanceof QueueChannel) {
count += ((QueueChannel) monitor).getQueueSize();
}
}
return count;
}
@ManagedAttribute
public String[] getChannelNames() {
return this.managementConfigurer.getChannelNames();
}
public MessageHandlerMetrics getHandlerMetrics(String name) {
return this.managementConfigurer.getHandlerMetrics(name);
}
public Statistics getHandlerDuration(String name) {
MessageHandlerMetrics handlerMetrics = getHandlerMetrics(name);
return handlerMetrics != null ? handlerMetrics.getDuration() : null;
}
@ManagedAttribute
public String[] getSourceNames() {
return this.managementConfigurer.getSourceNames();
}
public MessageSourceMetrics getSourceMetrics(String name) {
return this.managementConfigurer.getSourceMetrics(name);
}
public int getSourceMessageCount(String name) {
return (int) getSourceMessageCountLong(name);
}
public long getSourceMessageCountLong(String name) {
MessageSourceMetrics sourceMetrics = getSourceMetrics(name);
return sourceMetrics != null ? sourceMetrics.getMessageCountLong() : -1;
}
public MessageChannelMetrics getChannelMetrics(String name) {
return this.managementConfigurer.getChannelMetrics(name);
}
public int getChannelSendCount(String name) {
return (int) getChannelSendCountLong(name);
}
public long getChannelSendCountLong(String name) {
MessageChannelMetrics channelMetrics = getChannelMetrics(name);
return channelMetrics != null ? channelMetrics.getSendCountLong() : -1;
}
public int getChannelSendErrorCount(String name) {
return (int) getChannelSendErrorCountLong(name);
}
public long getChannelSendErrorCountLong(String name) {
MessageChannelMetrics channelMetrics = getChannelMetrics(name);
return channelMetrics != null ? channelMetrics.getSendErrorCountLong() : -1;
}
public int getChannelReceiveCount(String name) {
return (int) getChannelReceiveCountLong(name);
}
public long getChannelReceiveCountLong(String name) {
MessageChannelMetrics channelMetrics = getChannelMetrics(name);
if (channelMetrics instanceof PollableChannelManagement) {
return ((PollableChannelManagement) channelMetrics).getReceiveCountLong();
}
return -1;
}
@ManagedOperation
public Statistics getChannelSendRate(String name) {
MessageChannelMetrics channelMetrics = getChannelMetrics(name);
return channelMetrics != null ? channelMetrics.getSendRate() : null;
}
public Statistics getChannelErrorRate(String name) {
MessageChannelMetrics channelMetrics = getChannelMetrics(name);
return channelMetrics != null ? channelMetrics.getErrorRate() : null;
}
private void registerChannels() {
for (MessageChannelMetrics monitor : this.channels) {
String name = ((NamedComponent) monitor).getComponentName();
this.allChannelsByName.put(name, monitor);
if (!matches(this.componentNamePatterns, name)) {
continue;
}
String beanKey = getChannelBeanKey(name);
if (logger.isInfoEnabled()) {
logger.info("Registering MessageChannel " + name);
}
registerBeanNameOrInstance(monitor, beanKey);
}
}
private void registerHandlers() {
for (MessageHandlerMetrics handler : this.handlers) {
MessageHandlerMetrics monitor = enhanceHandlerMonitor(handler);
String name = monitor.getManagedName();
this.allHandlersByName.put(name, monitor);
if (!matches(this.componentNamePatterns, name)) {
continue;
}
String beanKey = getHandlerBeanKey(monitor);
if (logger.isInfoEnabled()) {
logger.info("Registering MessageHandler " + name);
}
registerBeanNameOrInstance(monitor, beanKey);
}
}
private void registerSources() {
for (MessageSourceMetrics source : this.sources) {
MessageSourceMetrics monitor = enhanceSourceMonitor(source);
String name = monitor.getManagedName();
this.allSourcesByName.put(name, monitor);
if (!matches(this.componentNamePatterns, name)) {
continue;
}
String beanKey = getSourceBeanKey(monitor);
if (logger.isInfoEnabled()) {
logger.info("Registering MessageSource " + name);
}
registerBeanNameOrInstance(monitor, beanKey);
}
}
private void registerEndpoints() {
String[] names = this.applicationContext.getBeanNamesForType(AbstractEndpoint.class);
Set<String> endpointNames = new HashSet<String>();
for (String name : names) {
if (!this.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);
ObjectName objectName = registerBeanInstance(new ManagedEndpoint(endpoint), beanKey);
if (logger.isInfoEnabled()) {
logger.info("Registered endpoint without MessageSource: " + objectName);
}
}
}
}
/**
* 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; //NOSONAR - intentional null return
}
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(this.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(this.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(this.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(this.domain + ":type=ManagedEndpoint,name=%s,bean=%s" + getStaticNames(), name, source);
}
private String getStaticNames() {
if (this.objectNameStaticProperties.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
for (Entry<Object, Object> entry : this.objectNameStaticProperties.entrySet()) {
builder.append("," + entry.getKey() + "=" + entry.getValue());
}
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.error("Could not get handler from bean = " + name);
}
}
}
Object field = getField(target, "inputChannel");
if (field != null) {
if (!this.anonymousHandlerCounters.containsKey(field)) {
this.anonymousHandlerCounters.put(field, new AtomicLong());
}
AtomicLong count = this.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) {
this.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;
if (monitor instanceof MessagingGatewaySupport && endpoint == monitor) {
field = monitor;
}
else {
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 (endpointName == null) {
endpoint = null;
}
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.error("Could not get handler from bean = " + name);
}
}
}
Object outputChannel = null;
if (target instanceof MessagingGatewaySupport) {
outputChannel = ((MessagingGatewaySupport) target).getRequestChannel();
}
else {
outputChannel = getField(target, "outputChannel");
}
if (outputChannel != null) {
if (!this.anonymousSourceCounters.containsKey(outputChannel)) {
this.anonymousSourceCounters.put(outputChannel, new AtomicLong());
}
AtomicLong count = this.anonymousSourceCounters.get(outputChannel);
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 = outputChannel + suffix;
source = "anonymous";
}
}
if (endpoint instanceof Lifecycle) {
// Wrap the monitor in a lifecycle so it exposes the start/stop operations
if (endpoint instanceof TrackableComponent) {
if (monitor instanceof MessageSourceManagement) {
result = new LifecycleTrackableMessageSourceManagement((Lifecycle) endpoint,
(MessageSourceManagement) monitor);
}
else {
result = new LifecycleTrackableMessageSourceMetrics((Lifecycle) endpoint, monitor);
}
}
else {
if (monitor instanceof MessageSourceManagement) {
result = new LifecycleMessageSourceManagement((Lifecycle) endpoint,
(MessageSourceManagement) monitor);
}
else {
result = new LifecycleMessageSourceMetrics((Lifecycle) endpoint, monitor);
}
}
}
if (name == null) {
name = monitor.toString();
source = "source";
}
if (endpointName != null) {
this.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);
}
}