diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java new file mode 100644 index 0000000000..7ed7dc12c5 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2011 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.jmx.config; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.PriorityOrdered; +import org.springframework.integration.monitor.IntegrationMBeanExporter; +import org.springframework.jmx.export.MBeanExporter; + +/** + * Most likely a temporary class mainly needed to address issue described in INT-2307. + * It helps in eliminating conflicts when more than one MBeanExporter is present. It creates a list + * of bean names that will be exported by the IntegrationMBeanExporter and merges it with the list + * of 'excludedBeans' of MBeanExporter so it will not attempt to export them again. + * + * @author Oleg Zhurakousky + * @since 2.1 + * + */ +class MBeanExporterHelper implements BeanFactoryPostProcessor, BeanPostProcessor, PriorityOrdered { + + private final static String EXCLUDED_BEANS_PROPERTY_NAME = "excludedBeans"; + + private final static String SI_ROOT_PACKAGE = "org.springframework.integration."; + + private final Set siBeanNames = new HashSet(); + + @SuppressWarnings("unchecked") + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof MBeanExporter && !(bean instanceof IntegrationMBeanExporter)){ + MBeanExporter mbeanExporter = (MBeanExporter) bean; + DirectFieldAccessor mbeDfa = new DirectFieldAccessor(mbeanExporter); + Set excludedNames = (Set) mbeDfa.getPropertyValue(EXCLUDED_BEANS_PROPERTY_NAME); + if (excludedNames != null) { + siBeanNames.addAll(excludedNames); + } + mbeDfa.setPropertyValue(EXCLUDED_BEANS_PROPERTY_NAME, siBeanNames); + } + return bean; + } + + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); + for (String beanName : beanDefinitionNames) { + BeanDefinition bd = beanFactory.getBeanDefinition(beanName); + String className = bd.getBeanClassName(); + if (className.startsWith(SI_ROOT_PACKAGE) && !(className.endsWith(IntegrationMBeanExporter.class.getName()))){ + siBeanNames.add(beanName); + } + } + } + + public int getOrder() { + return Integer.MIN_VALUE; + } +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java index 36ee82b3d2..75c9889fa0 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java @@ -13,6 +13,8 @@ package org.springframework.integration.jmx.config; +import java.util.UUID; + import javax.management.MBeanServerFactory; import org.w3c.dom.Element; @@ -21,15 +23,19 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.util.StringUtils; /** * Parser for the 'mbean-export' element of the integration JMX namespace. * * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { @@ -44,7 +50,7 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { @Override protected String getBeanClassName(Element element) { - return "org.springframework.integration.monitor.IntegrationMBeanExporter"; + return IntegrationMBeanExporter.class.getName(); } @Override @@ -56,8 +62,14 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "managed-components", "componentNamePatterns"); builder.addPropertyValue("server", mbeanServer); + this.registerMBeanExporterHelper(parserContext.getRegistry()); } + private void registerMBeanExporterHelper(BeanDefinitionRegistry registry){ + BeanDefinitionBuilder mBeanExporterHelperBuilder = BeanDefinitionBuilder.rootBeanDefinition(MBeanExporterHelper.class); + BeanDefinitionReaderUtils.registerWithGeneratedName(mBeanExporterHelperBuilder.getBeanDefinition(), registry); + } + private Object getMBeanServer(Element element, ParserContext parserContext) { String mbeanServer = element.getAttribute("server"); if (StringUtils.hasText(mbeanServer)) { @@ -77,6 +89,10 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { " (clashes with default). Please choose another bean id.", definition); } + if (id.matches(IntegrationMBeanExporter.class.getName() + "#[0-9]+")) { + // Randomize the name in case there are multiple contexts in the same JVM + id += "#" + UUID.randomUUID(); + } return id; } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java index e9d9e079f4..f2621dda66 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java @@ -90,6 +90,7 @@ import org.springframework.util.ReflectionUtils; * @author Dave Syer * @author Helena Edelson * @author Oleg Zhurakousky + * @author Gary Russell */ @ManagedResource public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostProcessor, BeanFactoryAware, @@ -516,7 +517,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP // Expose the raw bean if it is managed MessageChannel bean = monitor.getMessageChannel(); if (assembler.includeBean(bean.getClass(), monitor.getName())) { - registerBeanInstance(bean, monitor.getName()); + registerBeanInstance(bean, + this.getMonitoredIntegrationObjectBeanKey(bean, name)); } } } @@ -539,7 +541,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP // Expose the raw bean if it is managed MessageHandler bean = source.getMessageHandler(); if (assembler.includeBean(bean.getClass(), source.getName())) { - registerBeanInstance(bean, monitor.getName()); + registerBeanInstance(bean, + this.getMonitoredIntegrationObjectBeanKey(bean, name)); } } } @@ -562,7 +565,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP // Expose the raw bean if it is managed MessageSource bean = source.getMessageSource(); if (assembler.includeBean(bean.getClass(), source.getName())) { - registerBeanInstance(bean, monitor.getName()); + registerBeanInstance(bean, + this.getMonitoredIntegrationObjectBeanKey(bean, name)); } } } @@ -688,6 +692,11 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return String.format(domain + ":type=ManagedEndpoint,name=%s,bean=%s" + getStaticNames(), name, source); } + private String getMonitoredIntegrationObjectBeanKey(Object object, String name) { + // This ordering of keys seems to work with default settings of JConsole + return String.format(domain + ":type=" + object.getClass().getSimpleName() + ",name=%s" + getStaticNames(), name); + } + private String getStaticNames() { if (objectNameStaticProperties.isEmpty()) { return ""; diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java new file mode 100644 index 0000000000..de1a6e5b80 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2011 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_.mbeanexporterhelper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectInstance; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.jmx.export.MBeanExporter; + +/** + * @author Oleg Zhurakousky + * @author Gary Russell + * + */ +public class Int2307Tests { + + @Test + public void testInt2307_DefaultMBeanExporter() throws Exception{ + new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); + List servers = MBeanServerFactory.findMBeanServer(null); + assertEquals(1, servers.size()); + MBeanServer server = servers.get(0); + Set mbeans = server.queryMBeans(null, null); + int bits = 0; + int count = 0; + for (ObjectInstance mbean : mbeans) { + if (mbean.toString().startsWith("org.springframework.integration.router.RecipientListRouter[test.domain:type=RecipientListRouter,name=rlr,random=")) { + bits |= 1; + count++; + } else if (mbean.toString().startsWith("org.springframework.integration.monitor.LifecycleMessageHandlerMetrics[test.domain:type=MessageHandler,name=rlr,bean=endpoint,random=")) { + bits |= 2; + count++; + } else if (mbean.toString().startsWith("org.springframework.integration.router.HeaderValueRouter[test.domain:type=HeaderValueRouter,name=hvr,random=")) { + bits |= 4; + count++; + } else if (mbean.toString().startsWith("org.springframework.integration.monitor.LifecycleMessageHandlerMetrics[test.domain:type=MessageHandler,name=hvr,bean=endpoint,random=")) { + bits |= 8; + count++; + } + } + assertEquals(0xf, bits); + assertEquals(4, count); + + // make sure there are no duplicate MBean ObjectNames if 2 contexts loaded from same config + new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); + } + + @SuppressWarnings("unchecked") + @Test + public void testInt2307_CustomMBeanExporter() throws Exception{ + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("single-config-custom-exporter.xml", this.getClass()); + MBeanExporter exporter = context.getBean("myExporter", MBeanExporter.class); + Set excludedBeanNames = TestUtils.getPropertyValue(exporter, "excludedBeans", Set.class); + assertTrue(excludedBeanNames.contains("rlr")); + assertTrue(excludedBeanNames.contains("hvr")); + assertTrue(excludedBeanNames.contains("x")); + assertTrue(excludedBeanNames.contains("y")); + assertTrue(excludedBeanNames.contains("foo")); // non SI bean + } + + public static class Foo{} +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml new file mode 100644 index 0000000000..a3b926d6b3 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml @@ -0,0 +1,41 @@ + + + + + + + + #{new java.util.Random().nextInt()} + test + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml new file mode 100644 index 0000000000..4dd3005e3a --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml @@ -0,0 +1,37 @@ + + + + + + + + #{new java.util.Random().nextInt()} + test + + + + + + + + + + + + + + + +