Merge pull request #607 from garyrussell/INT-2487
* INT-2487: INT-2487 Object Name Patterns for Notifications
This commit is contained in:
@@ -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<ObjectName> 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<ObjectName> 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<ObjectName> retrieveMBeanNames() {
|
||||
List<ObjectName> objectNames = new ArrayList<ObjectName>();
|
||||
for (ObjectName pattern : this.objectNames) {
|
||||
Set<ObjectInstance> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.w3c.dom.Element;
|
||||
* @since 2.0
|
||||
*/
|
||||
public class NotificationListeningChannelAdapterParser extends AbstractChannelAdapterParser {
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean shouldGenerateIdAsFallback() {
|
||||
return true;
|
||||
|
||||
@@ -72,9 +72,12 @@
|
||||
|
||||
<xsd:element name="notification-listening-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines an inbound Channel Adapter that listens for JMX notifications.
|
||||
</xsd:documentation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Defines an inbound Channel Adapter that listens for JMX notifications. The 'object-name'
|
||||
attribute on this endpoint can contain an ObjectName pattern and the MBeanServer will
|
||||
be queried for MBeans with ObjectNames matching the pattern. In addition, it can contain
|
||||
a SpEL expression that references a <util:list/> of ObjectNames or ObjectName patterns.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
|
||||
@@ -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">
|
||||
|
||||
<context:mbean-export/>
|
||||
<context:mbean-server/>
|
||||
@@ -26,9 +24,32 @@
|
||||
|
||||
<bean id="testPublisher" class="org.springframework.integration.jmx.config.TestPublisher"/>
|
||||
|
||||
<bean id="testPublisher2" class="org.springframework.integration.jmx.config.TestPublisher"/>
|
||||
|
||||
<jmx:notification-listening-channel-adapter id="autoChannel"
|
||||
object-name="org.springframework.integration.jmx.config:type=TestPublisher,name=testPublisher"/>
|
||||
|
||||
<si:bridge input-channel="autoChannel" output-channel="nullChannel" />
|
||||
|
||||
<si:channel id="patternChannel">
|
||||
<si:queue/>
|
||||
</si:channel>
|
||||
|
||||
<jmx:notification-listening-channel-adapter id="patternAdapter"
|
||||
channel="patternChannel"
|
||||
object-name="org.springframework.integration.jmx.config:type=*,name=testPublisher"/>
|
||||
|
||||
<si:channel id="multiChannel">
|
||||
<si:queue/>
|
||||
</si:channel>
|
||||
|
||||
<jmx:notification-listening-channel-adapter id="multiAdapter"
|
||||
channel="multiChannel"
|
||||
object-name="#{patterns}"/>
|
||||
|
||||
<util:list id="patterns">
|
||||
<value>org.springframework.integration.jmx.config:type=*,name=testPublisher</value>
|
||||
<value>org.springframework.integration.jmx.config:type=*,name=testPub*</value>
|
||||
</util:list>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,21 @@
|
||||
event-driven and registered with the <interfacename>MBeanServer</interfacename>
|
||||
directly. It does not require any poller configuration.
|
||||
</para>
|
||||
<note>
|
||||
For this component only, the <emphasis>object-name</emphasis> 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 <emphasis>object-name</emphasis>
|
||||
attribute can contain a SpEL reference to a <util:list/> of ObjectName patterns:
|
||||
<programlisting><![CDATA[<jmx:notification-listening-channel-adapter id="manyNotificationsAdapter"
|
||||
channel="manyNotificationsChannel"
|
||||
object-name="#{patterns}"/>
|
||||
|
||||
<util:list id="patterns">
|
||||
<value>org.foo:type=Foo,name=*</value>
|
||||
<value>org.foo:type=Bar,name=*</value>
|
||||
</util:list>]]></programlisting>
|
||||
The names of the located MBean(s) will be logged when DEBUG level logging is enabled.
|
||||
</note>
|
||||
</section>
|
||||
|
||||
<section id="jmx-notification-publishing-channel-adapter">
|
||||
|
||||
Reference in New Issue
Block a user