INT-1003, INT-1004 notification publishing/listening adapters

This commit is contained in:
Mark Fisher
2010-03-08 19:54:17 +00:00
parent 29cf50d874
commit 88e6db6355
4 changed files with 98 additions and 48 deletions

View File

@@ -16,17 +16,21 @@
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.ObjectName;
import org.springframework.integration.core.Message;
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
* @since 2.0
*/
@@ -34,28 +38,29 @@ class DefaultNotificationMapper implements OutboundMessageMapper<Notification> {
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;
}
DefaultNotificationMapper(String sourceObjectName) {
this(stringToObjectName(sourceObjectName));
}
public void setDefaultNotificationType(String defaultNotificationType) {
this.defaultNotificationType = defaultNotificationType;
}
public Notification fromMessage(Message<?> message) throws Exception {
String type = this.resolveNotificationType(message);
return new Notification(type, this.sourceObjectName, this.sequence.incrementAndGet(),
message.getPayload().toString());
Assert.hasText(type,
"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) {
@@ -63,13 +68,4 @@ class DefaultNotificationMapper implements OutboundMessageMapper<Notification> {
return (type != null) ? type : this.defaultNotificationType;
}
private static ObjectName stringToObjectName(String objectName) {
try {
return ObjectNameManager.getInstance(objectName);
}
catch (MalformedObjectNameException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@@ -38,6 +38,9 @@ import org.springframework.integration.support.ComponentMetadata;
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
* @since 2.0
*/
@@ -54,22 +57,44 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
private volatile Object handback;
/**
* Provide a reference to the MBeanServer where the notification
* publishing MBeans are registered.
*/
public void setServer(MBeanServer 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) {
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) {
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) {
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) {
if (logger.isInfoEnabled()) {
logger.info("received notification: " + notification + ", and handback: " + handback);
@@ -88,10 +113,14 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
metadata.setAttribute("transport", "jmx");
}
/**
* Registers the notification listener with the specified ObjectNames.
*/
@Override
protected void doStart() {
try {
Assert.notNull(this.server, "MBeanServer is required.");
Assert.notEmpty(this.objectNames, "One or more ObjectNames are required.");
for (ObjectName objectName : this.objectNames) {
this.server.addNotificationListener(objectName, this, this.filter, this.handback);
}
@@ -101,10 +130,14 @@ public class NotificationListeningAdapter extends MessageProducerSupport impleme
}
}
/**
* Unregisters the notification listener.
*/
@Override
protected void doStop() {
try {
Assert.notNull(this.server, "MBeanServer is required.");
Assert.notEmpty(this.objectNames, "One or more ObjectNames are required.");
for (ObjectName objectName : this.objectNames) {
this.server.removeNotificationListener(objectName, this, this.filter, this.handback);
}

View File

@@ -18,13 +18,14 @@ package org.springframework.integration.jmx;
import java.util.Map;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.ObjectName;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.core.Message;
@@ -40,35 +41,53 @@ import org.springframework.util.Assert;
* @author Mark Fisher
* @since 2.0
*/
public class NotificationPublishingAdapter extends AbstractMessageHandler
implements BeanNameAware, BeanFactoryAware, InitializingBean {
public class NotificationPublishingAdapter extends AbstractMessageHandler implements BeanFactoryAware, InitializingBean {
private final PublisherDelegate delegate = new PublisherDelegate();
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;
public NotificationPublishingAdapter() {
this(null);
}
public NotificationPublishingAdapter(String objectName) {
public NotificationPublishingAdapter(ObjectName objectName) {
Assert.notNull(objectName, "JMX ObjectName is required");
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) {
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 {
@@ -83,14 +102,10 @@ public class NotificationPublishingAdapter extends AbstractMessageHandler
"No unique MBeanExporter is available in the current context (found " +
exporters.size() + ").");
MBeanExporter exporter = exporters.values().iterator().next();
if (this.objectName == null) {
this.objectName = "org.springframework.integration:" +
"type=notificationPublishingAdapter,name=" + this.beanName;
}
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()) {
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 volatile NotificationPublisher notificationPublisher;

View File

@@ -29,6 +29,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.integration.message.StringMessage;
import org.springframework.jmx.export.MBeanExporter;
@@ -49,13 +50,15 @@ public class NotificationPublishingAdapterTests {
@Before
public void setup() throws Exception {
this.publisherObjectName = ObjectNameManager.getInstance(
"org.springframework.integration:type=notificationPublishingAdapter,name=testPublisher");
this.publisherObjectName = ObjectNameManager.getInstance("test:type=publisher");
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();
MBeanExporter exporter = context.getBean(MBeanExporter.class);
exporter.getServer().addNotificationListener(this.publisherObjectName, this.listener, null, null);
exporter.getServer().addNotificationListener(publisherObjectName, this.listener, null, null);
}
@After
@@ -74,7 +77,7 @@ public class NotificationPublishingAdapterTests {
Notification notification = this.listener.notifications.get(0);
assertEquals(this.publisherObjectName, notification.getSource());
assertEquals("foo", notification.getMessage());
assertEquals("org.springframework.integration.jmx.event", notification.getType());
assertEquals("test.type", notification.getType());
}