diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotificationListeningMessageProducer.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotificationListeningMessageProducer.java index e1410dbcb3..5abefc01f1 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotificationListeningMessageProducer.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotificationListeningMessageProducer.java @@ -17,6 +17,11 @@ package org.springframework.integration.jmx; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; import javax.management.InstanceNotFoundException; import javax.management.ListenerNotFoundException; @@ -24,6 +29,7 @@ import javax.management.MBeanServerConnection; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; +import javax.management.ObjectInstance; import javax.management.ObjectName; import org.apache.commons.logging.Log; @@ -32,6 +38,7 @@ import org.springframework.integration.Message; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.support.MessageBuilder; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * A JMX {@link NotificationListener} implementation that will send Messages @@ -47,7 +54,7 @@ public class NotificationListeningMessageProducer extends MessageProducerSupport private volatile MBeanServerConnection server; - private volatile ObjectName objectName; + private volatile ObjectName[] objectNames; private volatile NotificationFilter filter; @@ -63,11 +70,13 @@ public class NotificationListeningMessageProducer extends MessageProducerSupport } /** - * Specify the JMX ObjectName of the notification publisher - * to which this notification listener should be subscribed. + * Specify the JMX ObjectNames (or patterns) + * of the notification publisher + * to which this notification listener should be subscribed. */ - public void setObjectName(ObjectName objectName) { - this.objectName = objectName; + public void setObjectName(ObjectName... objectNames) { + Assert.isTrue(!ObjectUtils.isEmpty(objectNames), "'objectNames' must contain at least one ObjectName"); + this.objectNames = objectNames; } /** @@ -114,10 +123,18 @@ public class NotificationListeningMessageProducer extends MessageProducerSupport */ @Override protected void doStart() { + logger.debug("Registering to receive notifications"); try { Assert.notNull(this.server, "MBeanServer is required."); - Assert.notNull(this.objectName, "An ObjectName is required."); - this.server.addNotificationListener(this.objectName, this, this.filter, this.handback); + Assert.notNull(this.objectNames, "An ObjectName is required."); + Collection objectNames = this.retrieveMBeanNames(); + if (objectNames.size() < 1) { + logger.error("No MBeans found matching ObjectName pattern(s): " + + Arrays.asList(this.objectNames)); + } + for (ObjectName objectName : objectNames) { + this.server.addNotificationListener(objectName, this, this.filter, this.handback); + } } catch (InstanceNotFoundException e) { throw new IllegalStateException("Failed to find MBean instance.", e); @@ -132,20 +149,46 @@ public class NotificationListeningMessageProducer extends MessageProducerSupport */ @Override protected void doStop() { - if (this.server != null && this.objectName != null) { - try { - this.server.removeNotificationListener(this.objectName, this, this.filter, this.handback); - } - catch (InstanceNotFoundException e) { - throw new IllegalStateException("Failed to find MBean instance.", e); - } - catch (ListenerNotFoundException e) { - throw new IllegalStateException("Failed to find NotificationListener.", e); - } - catch (IOException e) { - throw new IllegalStateException("IOException on MBeanServerConnection.", e); + logger.debug("Unregistering notifications"); + if (this.server != null && this.objectNames != null) { + Collection objectNames = this.retrieveMBeanNames(); + for (ObjectName objectName : objectNames) { + try { + this.server.removeNotificationListener(objectName, this, this.filter, this.handback); + } + catch (InstanceNotFoundException e) { + logger.error("Failed to find MBean instance.", e); + } + catch (ListenerNotFoundException e) { + logger.error("Failed to find NotificationListener.", e); + } + catch (IOException e) { + logger.error("IOException on MBeanServerConnection.", e); + } } } } + protected Collection retrieveMBeanNames() { + List objectNames = new ArrayList(); + for (ObjectName pattern : this.objectNames) { + Set mBeanInfos; + try { + mBeanInfos = this.server.queryMBeans(pattern, null); + } + catch (IOException e) { + throw new IllegalStateException("IOException on MBeanServerConnection.", e); + } + if (mBeanInfos.size() == 0 && logger.isDebugEnabled()) { + logger.debug("No MBeans found matching pattern:" + pattern); + } + for (ObjectInstance instance : mBeanInfos) { + if (logger.isDebugEnabled()) { + logger.debug("Found MBean:" + instance.getObjectName().toString()); + } + objectNames.add(instance.getObjectName()); + } + } + return objectNames; + } } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java index df0dc8d74d..258030b731 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java @@ -30,7 +30,7 @@ import org.w3c.dom.Element; * @since 2.0 */ public class NotificationListeningChannelAdapterParser extends AbstractChannelAdapterParser { - + @Override protected boolean shouldGenerateIdAsFallback() { return true; diff --git a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd index 05cd858d68..242d40e084 100644 --- a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd +++ b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd @@ -72,9 +72,12 @@ - - Defines an inbound Channel Adapter that listens for JMX notifications. - + of ObjectNames or ObjectName patterns. + ]]> diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests-context.xml index 9f4f7fa54b..9896a90e5a 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests-context.xml @@ -4,14 +4,12 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:si="http://www.springframework.org/schema/integration" xmlns:jmx="http://www.springframework.org/schema/integration/jmx" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context.xsd - http://www.springframework.org/schema/integration - http://www.springframework.org/schema/integration/spring-integration.xsd - http://www.springframework.org/schema/integration/jmx - http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd"> + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> @@ -26,9 +24,32 @@ + + + + + + + + + + + + + + + + org.springframework.integration.jmx.config:type=*,name=testPublisher + org.springframework.integration.jmx.config:type=*,name=testPub* + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests.java index 6c0ed7e581..596e025125 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import javax.management.Notification; @@ -37,6 +38,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ @ContextConfiguration @@ -46,9 +48,18 @@ public class NotificationListeningChannelAdapterParserTests { @Autowired private PollableChannel channel; + @Autowired + private PollableChannel patternChannel; + + @Autowired + private PollableChannel multiChannel; + @Autowired private TestPublisher testPublisher; + @Autowired + private TestPublisher testPublisher2; + @Autowired private MessageChannel autoChannel; @@ -59,10 +70,26 @@ public class NotificationListeningChannelAdapterParserTests { public void receiveNotification() throws Exception { assertNull(channel.receive(0)); testPublisher.send("ABC"); + verifyReceipt(channel, "testPublisher"); + verifyReceipt(patternChannel, "testPublisher"); + // multiChannel should see 2 copies + verifyReceipt(multiChannel, "testPublisher"); + verifyReceipt(multiChannel, "testPublisher"); + + testPublisher2.send("ABC"); + assertNull(channel.receive(0)); + assertNull(patternChannel.receive(0)); + // multiChannel should see only 1 copy + verifyReceipt(multiChannel, "testPublisher2"); + assertNull(multiChannel.receive(0)); + } + + private void verifyReceipt(PollableChannel channel, String beanName) { Message message = channel.receive(1000); assertNotNull(message); assertEquals(Notification.class, message.getPayload().getClass()); assertEquals("ABC", ((Notification) message.getPayload()).getMessage()); + assertTrue(((String) ((Notification) message.getPayload()).getSource()).endsWith(beanName)); } @Test diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestPublisher.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestPublisher.java index 0f95cc179c..2d9109a186 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestPublisher.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestPublisher.java @@ -18,6 +18,7 @@ package org.springframework.integration.jmx.config; import javax.management.Notification; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.export.notification.NotificationPublisher; import org.springframework.jmx.export.notification.NotificationPublisherAware; @@ -27,17 +28,22 @@ import org.springframework.jmx.export.notification.NotificationPublisherAware; * @since 2.0 */ @ManagedResource -public class TestPublisher implements NotificationPublisherAware { +public class TestPublisher implements NotificationPublisherAware, BeanNameAware { private volatile NotificationPublisher notificationPublisher; + private String beanName; + public void setNotificationPublisher(NotificationPublisher notificationPublisher) { this.notificationPublisher = notificationPublisher; } + public void setBeanName(String name) { + this.beanName = name; + } public void send(String s) { Notification notification = new Notification("test.type", - "org.springframework.integration.jmx.config:type=TestPublisher,name=testPublisher", 1, s); + "org.springframework.integration.jmx.config:type=TestPublisher,name=" + this.beanName, 1, s); this.notificationPublisher.sendNotification(notification); } diff --git a/src/reference/docbook/jmx.xml b/src/reference/docbook/jmx.xml index effe7f273b..9726bf6b43 100644 --- a/src/reference/docbook/jmx.xml +++ b/src/reference/docbook/jmx.xml @@ -56,6 +56,21 @@ event-driven and registered with the MBeanServer directly. It does not require any poller configuration. + + For this component only, the object-name attribute can contain an + ObjectName pattern (e.g. "org.foo:type=Bar,name=*") and the adapter will receive notifications + from all MBeans with ObjectNames that match the pattern. In addition, the object-name + attribute can contain a SpEL reference to a <util:list/> of ObjectName patterns: + + + + org.foo:type=Foo,name=* + org.foo:type=Bar,name=* +]]> + The names of the located MBean(s) will be logged when DEBUG level logging is enabled. +