From 1a56747a8abe3acda194950657422667ac56908a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 16 Dec 2011 09:11:22 -0500 Subject: [PATCH 1/2] INT-2307 added MBeanExporterHelper to avoid conflicts with core MBeanExporter INT-2307 polishing INT-2307 polishing --- .../jmx/config/MBeanExporterHelper.java | 80 +++++++++++++++++++ .../jmx/config/MBeanExporterParser.java | 8 ++ .../mbeanexporterhelper/Int2307Tests.java | 53 ++++++++++++ .../single-config-custom-exporter.xml | 32 ++++++++ .../mbeanexporterhelper/single-config.xml | 27 +++++++ 5 files changed, 200 insertions(+) create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml 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..1337250856 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 @@ -21,6 +21,8 @@ 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; @@ -56,8 +58,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)) { 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..397bc40b8c --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java @@ -0,0 +1,53 @@ +/* + * 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 java.util.Set; + +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.jmx.export.MBeanExporter; + +import static org.junit.Assert.assertTrue; + +/** + * @author Oleg Zhurakousky + * + */ +public class Int2307Tests { + + @Test + public void testInt2307_DefaultMBeanExporter() throws Exception{ + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); + context.destroy(); + } + + @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 + context.destroy(); + } + + 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..670e34a37a --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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..fffedc1c31 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + From cfc37616e591330bfb06c4e3f29a2a7f88b52dbb Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Fri, 16 Dec 2011 22:03:11 -0500 Subject: [PATCH 2/2] INT-2313 Avoid Naming Conflicts With Mutliple Ctx If multiple application contexts exist in the same JVM, where managed beans exist with the same name, InstanceAlreadyExistsExceptions could result. 1. Change int-jmx MBean exporter parser to ensure the exporter itself has a unique name within the JVM. 2. When the IMBE exports Integration components for monitoring, where the component itself is already an MBean, add domain and object name static properties configured in the MBean exporter. 3. Add tests to load the same ac a second time to ensure no naming conflicts arise. --- .../jmx/config/MBeanExporterParser.java | 10 ++++- .../monitor/IntegrationMBeanExporter.java | 15 +++++-- .../mbeanexporterhelper/Int2307Tests.java | 43 ++++++++++++++++--- .../single-config-custom-exporter.xml | 13 +++++- .../mbeanexporterhelper/single-config.xml | 12 +++++- 5 files changed, 79 insertions(+), 14 deletions(-) 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 1337250856..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; @@ -26,12 +28,14 @@ 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 { @@ -46,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 @@ -85,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 index 397bc40b8c..de1a6e5b80 100644 --- 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 @@ -12,27 +12,57 @@ */ 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 org.junit.Test; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectInstance; -import org.springframework.context.ApplicationContext; +import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.test.util.TestUtils; import org.springframework.jmx.export.MBeanExporter; -import static org.junit.Assert.assertTrue; - /** * @author Oleg Zhurakousky + * @author Gary Russell * */ public class Int2307Tests { @Test public void testInt2307_DefaultMBeanExporter() throws Exception{ - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); - context.destroy(); + 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") @@ -46,7 +76,6 @@ public class Int2307Tests { assertTrue(excludedBeanNames.contains("x")); assertTrue(excludedBeanNames.contains("y")); assertTrue(excludedBeanNames.contains("foo")); // non SI bean - context.destroy(); } 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 index 670e34a37a..a3b926d6b3 100644 --- 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 @@ -3,15 +3,24 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-jmx="http://www.springframework.org/schema/integration/jmx" + xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx-2.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> - - + + + + #{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 index fffedc1c31..4dd3005e3a 100644 --- 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 @@ -3,14 +3,24 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-jmx="http://www.springframework.org/schema/integration/jmx" + xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx-2.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> - + + + + #{new java.util.Random().nextInt()} + test + +