package org.oddjob.jmx; import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import javax.management.MBeanServerConnection; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.oddjob.FailedToStopException; import org.oddjob.framework.SimpleService; import org.oddjob.state.IsAnyState; import org.oddjob.state.ServiceState; /** * Shared implementation for JMX clients. * * @author rob * */ abstract public class ClientBase extends SimpleService { protected enum WhyStop { STOP_REQUEST, SERVER_STOPPED, HEARTBEAT_FAILURE } /** The notification processor. */ private volatile ScheduledExecutorService notificationProcessor; /** * @oddjob.property * @oddjob.description The JMX service URL. This is can be either * the full blown convoluted JMX Service URL starting * <code>service.jmx....</code> or it can just be the last part of the * form <code>hostname[:port][/instance-name]</code>. * @oddjob.required No. If not provided the client connects to the Platform * MBean Server for the current VM. */ private String connection; /** The connector */ private volatile JMXConnector cntor; /** * @oddjob.property * @oddjob.description The heart beat interval, in milliseconds. * @oddjob.required Not, defaults to 5 seconds. */ private long heartbeat = 5000; /** * @oddjob.property * @oddjob.description The environment. Typically username/password * credentials. * @oddjob.required No. */ private Map<String, ?> environment; /** * Construct a new instance. * */ public ClientBase() { } /** * * @throws Exception */ protected void onStart() throws Exception { MBeanServerConnection mbsc; if (connection == null) { logger().info("Connecting to the Platform MBean Server..."); mbsc = ManagementFactory.getPlatformMBeanServer(); } else { logger().info("Connecting to [" + connection + "] ..."); JMXServiceURL address = new JMXServiceURLHelper().parse(connection); cntor = JMXConnectorFactory.connect(address, environment); mbsc = cntor.getMBeanServerConnection(); cntor.addConnectionNotificationListener( new ServerStoppedListener(), null, null); } notificationProcessor = Executors.newSingleThreadScheduledExecutor(); doStart(mbsc, notificationProcessor); } /** * Overridden by subclasses to provide a specific startup. * * @param mbsc * @param notificationProcessor * @throws Exception */ abstract protected void doStart(MBeanServerConnection mbsc, ScheduledExecutorService notificationProcessor) throws Exception; @Override protected void onStop() throws FailedToStopException { doStop(WhyStop.STOP_REQUEST, null); } protected void doStop(final WhyStop why, final Exception cause) { // There is a small possibility that the SERVER_STOPPED and // HEARTBEAT_FAIURE happen simultaneously. ExecutorService notificationProcessor; synchronized (this) { notificationProcessor = this.notificationProcessor; if (notificationProcessor == null) { return; } this.notificationProcessor = null; } notificationProcessor.shutdownNow(); onStop(why); if (why == WhyStop.STOP_REQUEST && cntor != null) { try { cntor.close(); } catch (IOException e) { logger().debug("Failed to close connection: " + e); } } cntor = null; stateHandler().waitToWhen(new IsAnyState(), new Runnable() { public void run() { switch (why) { case HEARTBEAT_FAILURE: getStateChanger().setStateException(cause); logger().error( "Client stopped because of heartbeat Failure.", cause); break; case SERVER_STOPPED: getStateChanger().setStateException( new Exception("Server Stopped.")); logger().info("Client stopped because server Stopped."); break; default: getStateChanger().setState(ServiceState.STOPPED); logger().debug( "Client stopped because stop was requested."); } } }); } abstract protected void onStop(WhyStop why); /** * Set naming service url. * * @param connection The name of the remote node in the naming service. */ public void setConnection(String lookup) { this.connection = lookup; } /** * Get the JMX service URL. * * @return The name of the remote node in the naming service. */ public String getConnection() { return connection; } public Map<String, ?> getEnvironment() { return environment; } public void setEnvironment(Map<String, ?> environment) { this.environment = environment; } public long getHeartbeat() { return heartbeat; } public void setHeartbeat(long heartbeat) { this.heartbeat = heartbeat; } /** * Listener to detect when server has stopped. */ class ServerStoppedListener implements NotificationListener { @Override public void handleNotification(Notification notification, Object handback) { String notificationType = notification.getType(); logger().debug("Connection Notification Listener recevied: " + notificationType); if (JMXConnectionNotification.CLOSED.equals(notificationType)) { try { doStop(WhyStop.SERVER_STOPPED, null); } catch (Exception e1) { logger().error( "Failed to stop from Connection Notification Listener:", e1); } } } } }