/*
* Copyright 2002-2016 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;
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 java.util.concurrent.atomic.AtomicBoolean;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
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;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A JMX {@link NotificationListener} implementation that will send Messages
* containing the JMX {@link Notification} instances as their payloads.
*
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class NotificationListeningMessageProducer extends MessageProducerSupport
implements NotificationListener, ApplicationListener<ContextRefreshedEvent> {
private final Log logger = LogFactory.getLog(this.getClass());
private final AtomicBoolean listenerRegisteredOnStartup = new AtomicBoolean();
private volatile MBeanServerConnection server;
private volatile ObjectName[] objectNames;
private volatile NotificationFilter filter;
private volatile Object handback;
/**
* Provide a reference to the MBeanServer where the notification
* publishing MBeans are registered.
*
* @param server the MBean server connection.
*/
public void setServer(MBeanServerConnection server) {
this.server = server;
}
/**
* Specify the JMX ObjectNames (or patterns)
* of the notification publisher
* to which this notification listener should be subscribed.
*
* @param objectNames The object names.
*/
public void setObjectName(ObjectName... objectNames) {
Assert.isTrue(!ObjectUtils.isEmpty(objectNames), "'objectNames' must contain at least one ObjectName");
this.objectNames = objectNames;
}
/**
* Specify a {@link NotificationFilter} to be passed to the server
* when registering this listener. The filter may be null.
*
* @param filter The filter.
*/
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.
*
* @param handback The object.
*/
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.
*/
@Override
public void handleNotification(Notification notification, Object handback) {
if (this.logger.isInfoEnabled()) {
this.logger.info("received notification: " + notification + ", and handback: " + handback);
}
AbstractIntegrationMessageBuilder<?> builder = this.getMessageBuilderFactory().withPayload(notification);
if (handback != null) {
builder.setHeader(JmxHeaders.NOTIFICATION_HANDBACK, handback);
}
Message<?> message = builder.build();
this.sendMessage(message);
}
@Override
public String getComponentType() {
return "jmx:notification-listening-channel-adapter";
}
/**
* The {@link NotificationListener} might not be registered on {@link #start()}
* because the {@code MBeanExporter} might not been started yet.
* @param event the ContextRefreshedEvent event
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!this.listenerRegisteredOnStartup.getAndSet(true) && isAutoStartup()) {
doStart();
}
}
/**
* Registers the notification listener with the specified ObjectNames.
*/
@Override
protected void doStart() {
if (!this.listenerRegisteredOnStartup.get()) {
return;
}
this.logger.debug("Registering to receive notifications");
try {
Assert.notNull(this.server, "MBeanServer is required.");
Assert.notNull(this.objectNames, "An ObjectName is required.");
Collection<ObjectName> objectNames = this.retrieveMBeanNames();
if (objectNames.size() < 1) {
this.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);
}
catch (IOException e) {
throw new IllegalStateException("IOException on MBeanServerConnection.", e);
}
}
/**
* Unregisters the notification listener.
*/
@Override
protected void doStop() {
this.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) {
this.logger.error("Failed to find MBean instance.", e);
}
catch (ListenerNotFoundException e) {
this.logger.error("Failed to find NotificationListener.", e);
}
catch (IOException e) {
this.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 && this.logger.isDebugEnabled()) {
this.logger.debug("No MBeans found matching pattern:" + pattern);
}
for (ObjectInstance instance : mBeanInfos) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found MBean:" + instance.getObjectName().toString());
}
objectNames.add(instance.getObjectName());
}
}
return objectNames;
}
}