INT-1003, INT-1004 notification publishing/listening adapters
This commit is contained in:
@@ -16,17 +16,21 @@
|
|||||||
|
|
||||||
package org.springframework.integration.jmx;
|
package org.springframework.integration.jmx;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import javax.management.MalformedObjectNameException;
|
|
||||||
import javax.management.Notification;
|
import javax.management.Notification;
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
import org.springframework.integration.core.Message;
|
import org.springframework.integration.core.Message;
|
||||||
import org.springframework.integration.message.OutboundMessageMapper;
|
import org.springframework.integration.message.OutboundMessageMapper;
|
||||||
import org.springframework.jmx.support.ObjectNameManager;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Default Messaging Mapper implementation for the {@link NotificationPublishingAdapter}.
|
||||||
|
* If the Message has a String-typed payload, that will be passed as the 'message' of
|
||||||
|
* the Notification instance. Otherwise, the payload object will be passed as the
|
||||||
|
* 'userData' of the Notification instance.
|
||||||
|
*
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
@@ -34,28 +38,29 @@ class DefaultNotificationMapper implements OutboundMessageMapper<Notification> {
|
|||||||
|
|
||||||
private final ObjectName sourceObjectName;
|
private final ObjectName sourceObjectName;
|
||||||
|
|
||||||
private volatile String defaultNotificationType = "org.springframework.integration.jmx.event";
|
private final String defaultNotificationType;
|
||||||
|
|
||||||
private final AtomicInteger sequence = new AtomicInteger();
|
private final AtomicLong sequence = new AtomicLong();
|
||||||
|
|
||||||
|
|
||||||
DefaultNotificationMapper(ObjectName sourceObjectName) {
|
DefaultNotificationMapper(ObjectName sourceObjectName, String defaultNotificationType) {
|
||||||
this.sourceObjectName = sourceObjectName;
|
this.sourceObjectName = sourceObjectName;
|
||||||
}
|
|
||||||
|
|
||||||
DefaultNotificationMapper(String sourceObjectName) {
|
|
||||||
this(stringToObjectName(sourceObjectName));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDefaultNotificationType(String defaultNotificationType) {
|
|
||||||
this.defaultNotificationType = defaultNotificationType;
|
this.defaultNotificationType = defaultNotificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Notification fromMessage(Message<?> message) throws Exception {
|
public Notification fromMessage(Message<?> message) throws Exception {
|
||||||
String type = this.resolveNotificationType(message);
|
String type = this.resolveNotificationType(message);
|
||||||
return new Notification(type, this.sourceObjectName, this.sequence.incrementAndGet(),
|
Assert.hasText(type,
|
||||||
message.getPayload().toString());
|
"No notification type header is available, and no default has been provided.");
|
||||||
|
Object payload = (message != null) ? message.getPayload() : null;
|
||||||
|
String notificationMessage = (payload instanceof String) ? (String) payload : null;
|
||||||
|
Notification notification = new Notification(type, this.sourceObjectName,
|
||||||
|
this.sequence.incrementAndGet(), System.currentTimeMillis(), notificationMessage);
|
||||||
|
if (payload != null && !(payload instanceof String)) {
|
||||||
|
notification.setUserData(payload);
|
||||||
|
}
|
||||||
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveNotificationType(Message<?> message) {
|
private String resolveNotificationType(Message<?> message) {
|
||||||
@@ -63,13 +68,4 @@ class DefaultNotificationMapper implements OutboundMessageMapper<Notification> {
|
|||||||
return (type != null) ? type : this.defaultNotificationType;
|
return (type != null) ? type : this.defaultNotificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectName stringToObjectName(String objectName) {
|
|
||||||
try {
|
|
||||||
return ObjectNameManager.getInstance(objectName);
|
|
||||||
}
|
|
||||||
catch (MalformedObjectNameException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ import org.springframework.integration.support.ComponentMetadata;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A JMX {@link NotificationListener} implementation that will send Messages
|
||||||
|
* containing the JMX {@link Notification} instances as their payloads.
|
||||||
|
*
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
@@ -54,22 +57,44 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
|
|||||||
private volatile Object handback;
|
private volatile Object handback;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a reference to the MBeanServer where the notification
|
||||||
|
* publishing MBeans are registered.
|
||||||
|
*/
|
||||||
public void setServer(MBeanServer server) {
|
public void setServer(MBeanServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify one or more JMX ObjectNames of notification publishers
|
||||||
|
* to which this notification listener should be subscribed.
|
||||||
|
*/
|
||||||
public void setObjectNames(ObjectName... objectNames) {
|
public void setObjectNames(ObjectName... objectNames) {
|
||||||
this.objectNames = new LinkedHashSet<ObjectName>(Arrays.asList(objectNames));
|
this.objectNames = new LinkedHashSet<ObjectName>(Arrays.asList(objectNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a {@link NotificationFilter} to be passed to the server
|
||||||
|
* when registering this listener. The filter may be null.
|
||||||
|
*/
|
||||||
public void setFilter(NotificationFilter filter) {
|
public void setFilter(NotificationFilter filter) {
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a handback object to provide context to the listener
|
||||||
|
* upon notification. This object may be null.
|
||||||
|
*/
|
||||||
public void setHandback(Object handback) {
|
public void setHandback(Object handback) {
|
||||||
this.handback = handback;
|
this.handback = handback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification handling method implementation. Creates a Message with the
|
||||||
|
* JMX {@link Notification} as its payload, and if the handback object is
|
||||||
|
* not null, it sets that as a Message header value. The Message is then
|
||||||
|
* sent to this producer's output channel.
|
||||||
|
*/
|
||||||
public void handleNotification(Notification notification, Object handback) {
|
public void handleNotification(Notification notification, Object handback) {
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("received notification: " + notification + ", and handback: " + handback);
|
logger.info("received notification: " + notification + ", and handback: " + handback);
|
||||||
@@ -88,10 +113,14 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
|
|||||||
metadata.setAttribute("transport", "jmx");
|
metadata.setAttribute("transport", "jmx");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the notification listener with the specified ObjectNames.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void doStart() {
|
protected void doStart() {
|
||||||
try {
|
try {
|
||||||
Assert.notNull(this.server, "MBeanServer is required.");
|
Assert.notNull(this.server, "MBeanServer is required.");
|
||||||
|
Assert.notEmpty(this.objectNames, "One or more ObjectNames are required.");
|
||||||
for (ObjectName objectName : this.objectNames) {
|
for (ObjectName objectName : this.objectNames) {
|
||||||
this.server.addNotificationListener(objectName, this, this.filter, this.handback);
|
this.server.addNotificationListener(objectName, this, this.filter, this.handback);
|
||||||
}
|
}
|
||||||
@@ -101,10 +130,14 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters the notification listener.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void doStop() {
|
protected void doStop() {
|
||||||
try {
|
try {
|
||||||
Assert.notNull(this.server, "MBeanServer is required.");
|
Assert.notNull(this.server, "MBeanServer is required.");
|
||||||
|
Assert.notEmpty(this.objectNames, "One or more ObjectNames are required.");
|
||||||
for (ObjectName objectName : this.objectNames) {
|
for (ObjectName objectName : this.objectNames) {
|
||||||
this.server.removeNotificationListener(objectName, this, this.filter, this.handback);
|
this.server.removeNotificationListener(objectName, this, this.filter, this.handback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ package org.springframework.integration.jmx;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.management.MalformedObjectNameException;
|
||||||
import javax.management.Notification;
|
import javax.management.Notification;
|
||||||
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.ListableBeanFactory;
|
import org.springframework.beans.factory.ListableBeanFactory;
|
||||||
import org.springframework.integration.core.Message;
|
import org.springframework.integration.core.Message;
|
||||||
@@ -40,35 +41,53 @@ import org.springframework.util.Assert;
|
|||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public class NotificationPublishingAdapter extends AbstractMessageHandler
|
public class NotificationPublishingAdapter extends AbstractMessageHandler implements BeanFactoryAware, InitializingBean {
|
||||||
implements BeanNameAware, BeanFactoryAware, InitializingBean {
|
|
||||||
|
|
||||||
private final PublisherDelegate delegate = new PublisherDelegate();
|
private final PublisherDelegate delegate = new PublisherDelegate();
|
||||||
|
|
||||||
private volatile OutboundMessageMapper<Notification> notificationMapper;
|
private volatile OutboundMessageMapper<Notification> notificationMapper;
|
||||||
|
|
||||||
private volatile String objectName;
|
private final ObjectName objectName;
|
||||||
|
|
||||||
private volatile String beanName;
|
private volatile String defaultNotificationType;
|
||||||
|
|
||||||
private volatile ListableBeanFactory beanFactory;
|
private volatile ListableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
public NotificationPublishingAdapter() {
|
public NotificationPublishingAdapter(ObjectName objectName) {
|
||||||
this(null);
|
Assert.notNull(objectName, "JMX ObjectName is required");
|
||||||
}
|
|
||||||
|
|
||||||
public NotificationPublishingAdapter(String objectName) {
|
|
||||||
this.objectName = objectName;
|
this.objectName = objectName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NotificationPublishingAdapter(String objectName) {
|
||||||
|
Assert.notNull(objectName, "JMX ObjectName is required");
|
||||||
|
try {
|
||||||
|
this.objectName = ObjectNameManager.getInstance(objectName);
|
||||||
|
}
|
||||||
|
catch (MalformedObjectNameException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a mapper for creating Notifications from a Message. If not provided,
|
||||||
|
* a default implementation will be used such that String-typed payloads will be
|
||||||
|
* passed as the 'message' of the Notification and all other payload types
|
||||||
|
* will be passed as the 'userData' of the Notification.
|
||||||
|
*/
|
||||||
public void setNotificationMapper(OutboundMessageMapper<Notification> notificationMapper) {
|
public void setNotificationMapper(OutboundMessageMapper<Notification> notificationMapper) {
|
||||||
this.notificationMapper = notificationMapper;
|
this.notificationMapper = notificationMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBeanName(String beanName) {
|
/**
|
||||||
this.beanName = beanName;
|
* Specify a dot-delimited String representing the Notification type to
|
||||||
|
* use by default when <emphasis>no</emphasis> explicit Notification mapper
|
||||||
|
* has been configured. If not provided, then a notification type header will
|
||||||
|
* be required for each message being mapped into a Notification.
|
||||||
|
*/
|
||||||
|
public void setDefaultNotificationType(String defaultNotificationType) {
|
||||||
|
this.defaultNotificationType = defaultNotificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||||
@@ -83,14 +102,10 @@ public class NotificationPublishingAdapter extends AbstractMessageHandler
|
|||||||
"No unique MBeanExporter is available in the current context (found " +
|
"No unique MBeanExporter is available in the current context (found " +
|
||||||
exporters.size() + ").");
|
exporters.size() + ").");
|
||||||
MBeanExporter exporter = exporters.values().iterator().next();
|
MBeanExporter exporter = exporters.values().iterator().next();
|
||||||
if (this.objectName == null) {
|
|
||||||
this.objectName = "org.springframework.integration:" +
|
|
||||||
"type=notificationPublishingAdapter,name=" + this.beanName;
|
|
||||||
}
|
|
||||||
if (this.notificationMapper == null) {
|
if (this.notificationMapper == null) {
|
||||||
this.notificationMapper = new DefaultNotificationMapper(this.objectName);
|
this.notificationMapper = new DefaultNotificationMapper(this.objectName, this.defaultNotificationType);
|
||||||
}
|
}
|
||||||
exporter.registerManagedResource(this.delegate, ObjectNameManager.getInstance(this.objectName));
|
exporter.registerManagedResource(this.delegate, this.objectName);
|
||||||
if (this.logger.isInfoEnabled()) {
|
if (this.logger.isInfoEnabled()) {
|
||||||
this.logger.info("Registered JMX notification publisher as MBean with ObjectName: " + this.objectName);
|
this.logger.info("Registered JMX notification publisher as MBean with ObjectName: " + this.objectName);
|
||||||
}
|
}
|
||||||
@@ -102,6 +117,9 @@ public class NotificationPublishingAdapter extends AbstractMessageHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple class used for the actual MBean instances to be registered.
|
||||||
|
*/
|
||||||
private static class PublisherDelegate implements NotificationPublisherAware {
|
private static class PublisherDelegate implements NotificationPublisherAware {
|
||||||
|
|
||||||
private volatile NotificationPublisher notificationPublisher;
|
private volatile NotificationPublisher notificationPublisher;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.context.support.StaticApplicationContext;
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
import org.springframework.integration.message.StringMessage;
|
import org.springframework.integration.message.StringMessage;
|
||||||
import org.springframework.jmx.export.MBeanExporter;
|
import org.springframework.jmx.export.MBeanExporter;
|
||||||
@@ -49,13 +50,15 @@ public class NotificationPublishingAdapterTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
this.publisherObjectName = ObjectNameManager.getInstance(
|
this.publisherObjectName = ObjectNameManager.getInstance("test:type=publisher");
|
||||||
"org.springframework.integration:type=notificationPublishingAdapter,name=testPublisher");
|
|
||||||
context.registerSingleton("exporter", MBeanExporter.class);
|
context.registerSingleton("exporter", MBeanExporter.class);
|
||||||
context.registerSingleton("testPublisher", NotificationPublishingAdapter.class);
|
RootBeanDefinition publisherDefinition = new RootBeanDefinition(NotificationPublishingAdapter.class);
|
||||||
|
publisherDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.publisherObjectName);
|
||||||
|
publisherDefinition.getPropertyValues().add("defaultNotificationType", "test.type");
|
||||||
|
context.registerBeanDefinition("testPublisher", publisherDefinition);
|
||||||
context.refresh();
|
context.refresh();
|
||||||
MBeanExporter exporter = context.getBean(MBeanExporter.class);
|
MBeanExporter exporter = context.getBean(MBeanExporter.class);
|
||||||
exporter.getServer().addNotificationListener(this.publisherObjectName, this.listener, null, null);
|
exporter.getServer().addNotificationListener(publisherObjectName, this.listener, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -74,7 +77,7 @@ public class NotificationPublishingAdapterTests {
|
|||||||
Notification notification = this.listener.notifications.get(0);
|
Notification notification = this.listener.notifications.get(0);
|
||||||
assertEquals(this.publisherObjectName, notification.getSource());
|
assertEquals(this.publisherObjectName, notification.getSource());
|
||||||
assertEquals("foo", notification.getMessage());
|
assertEquals("foo", notification.getMessage());
|
||||||
assertEquals("org.springframework.integration.jmx.event", notification.getType());
|
assertEquals("test.type", notification.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user