/* * (c) Rob Gordon 2005 */ package org.oddjob.jmx.handlers; import java.io.Serializable; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ReflectionException; import org.oddjob.Stateful; import org.oddjob.framework.JobDestroyedException; import org.oddjob.jmx.RemoteOperation; import org.oddjob.jmx.client.ClientDestroyed; import org.oddjob.jmx.client.ClientHandlerResolver; import org.oddjob.jmx.client.ClientInterfaceHandlerFactory; import org.oddjob.jmx.client.ClientSideToolkit; import org.oddjob.jmx.client.Destroyable; import org.oddjob.jmx.client.HandlerVersion; import org.oddjob.jmx.client.SimpleHandlerResolver; import org.oddjob.jmx.client.Synchronizer; import org.oddjob.jmx.server.JMXOperationPlus; import org.oddjob.jmx.server.ServerInterfaceHandler; import org.oddjob.jmx.server.ServerInterfaceHandlerFactory; import org.oddjob.jmx.server.ServerSideToolkit; import org.oddjob.state.JobState; import org.oddjob.state.State; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; public class StatefulHandlerFactory implements ServerInterfaceHandlerFactory<Stateful, Stateful> { public static final HandlerVersion VERSION = new HandlerVersion(3, 0); public static final String STATE_CHANGE_NOTIF_TYPE = "org.oddjob.statechange"; static final JMXOperationPlus<Notification[]> SYNCHRONIZE = new JMXOperationPlus<Notification[]>( "statefulSynchronize", "Sychronize Notifications.", Notification[].class, MBeanOperationInfo.INFO); private static final JMXOperationPlus<StateData> LAST_STATE_EVENT = new JMXOperationPlus<StateData>( "lastStateEvent", "Get Last State Event.", StateData.class, MBeanOperationInfo.INFO); public Class<Stateful> interfaceClass() { return Stateful.class; } public MBeanAttributeInfo[] getMBeanAttributeInfo() { return new MBeanAttributeInfo[0]; } public MBeanOperationInfo[] getMBeanOperationInfo() { return new MBeanOperationInfo[] { SYNCHRONIZE.getOpInfo(), LAST_STATE_EVENT.getOpInfo(), }; } public MBeanNotificationInfo[] getMBeanNotificationInfo() { MBeanNotificationInfo[] nInfo = new MBeanNotificationInfo[] { new MBeanNotificationInfo( new String[] { STATE_CHANGE_NOTIF_TYPE }, Notification.class.getName(), "State change notification.") }; return nInfo; } public ServerInterfaceHandler createServerHandler(Stateful stateful, ServerSideToolkit ojmb) { ServerStateHandler stateHelper = new ServerStateHandler(stateful, ojmb); try { stateful.addStateListener(stateHelper); } catch (JobDestroyedException e) { stateHelper.jobStateChange(stateful.lastStateEvent()); } return stateHelper; } public ClientHandlerResolver<Stateful> clientHandlerFactory() { return new SimpleHandlerResolver<Stateful>( ClientStatefulHandlerFactory.class.getName(), VERSION); } public static class ClientStatefulHandlerFactory implements ClientInterfaceHandlerFactory<Stateful> { public Class<Stateful> interfaceClass() { return Stateful.class; } public HandlerVersion getVersion() { return VERSION; } public Stateful createClientHandler(Stateful proxy, ClientSideToolkit toolkit) { return new ClientStatefulHandler(proxy, toolkit); } } /** * Implement a remote state listener. This handles remote state events and also * propagates them on the client side as normal state events. * * @author Rob Gordon */ static class ClientStatefulHandler implements Stateful, Destroyable { /** Remember the last event so new state listeners can be told it. */ private StateEvent lastEvent; /** State listeners */ private final List<StateListener> listeners = new ArrayList<StateListener>(); private final ClientSideToolkit toolkit; /** The owner, to be used as the source of the event. */ private final Stateful owner; private Synchronizer synchronizer; /** * Constructor. * * @param owner The owning (source) object. */ public ClientStatefulHandler(Stateful owner, ClientSideToolkit toolkit) { this.owner = owner; this.toolkit = toolkit; lastEvent = new StateEvent(this.owner, JobState.READY, null); } StateEvent dataToEvent(StateData data) { return new StateEvent(owner, data.getJobState(), data.getDate(), data.getThrowable()); } void jobStateChange(StateData data) { StateEvent newEvent = dataToEvent(data); lastEvent = newEvent; List<StateListener> copy = null; synchronized (listeners) { copy = new ArrayList<StateListener>(listeners); } for (StateListener listener : copy) { listener.jobStateChange(newEvent); } } /** * Add a job state listener. * * @param listener The job state listener. */ public void addStateListener(StateListener listener) throws JobDestroyedException { synchronized (this) { if (synchronizer == null) { synchronizer = new Synchronizer( new NotificationListener() { public void handleNotification(Notification notification, Object arg1) { StateData stateData = (StateData) notification.getUserData(); jobStateChange(stateData); } }); toolkit.registerNotificationListener( STATE_CHANGE_NOTIF_TYPE, synchronizer); Notification[] lastNotifications = null; try { lastNotifications = (Notification[]) toolkit.invoke(SYNCHRONIZE); } catch (InstanceNotFoundException e) { throw new JobDestroyedException(owner); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } synchronizer.synchronize(lastNotifications); } if (lastEvent.getState().isDestroyed()) { throw new JobDestroyedException(owner); } StateEvent nowEvent = lastEvent; listener.jobStateChange(nowEvent); listeners.add(listener); } } /** * Remove a job state listener. * * @param listener The job state listener. */ public void removeStateListener(StateListener listener) { synchronized (this) { listeners.remove(listener); if (listeners.size() == 0) { toolkit.removeNotificationListener(STATE_CHANGE_NOTIF_TYPE, synchronizer); synchronizer = null; } } } @Override public StateEvent lastStateEvent() { synchronized (this) { if (!listeners.isEmpty()) { return lastEvent; } } try { return dataToEvent(toolkit.invoke(LAST_STATE_EVENT)); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void destroy() { jobStateChange(new StateData( new ClientDestroyed(), new Date(), null)); } } class ServerStateHandler implements StateListener, ServerInterfaceHandler { private final Stateful stateful; private final ServerSideToolkit toolkit; /** Remember last event. */ private Notification lastNotification; ServerStateHandler(Stateful stateful, ServerSideToolkit ojmb) { this.stateful = stateful; this.toolkit = ojmb; } /* * (non-Javadoc) * * @see org.oddjob.state.AbstractJobStateListener#jobStateChange(org.oddjob.state.JobStateEvent) */ public void jobStateChange(final StateEvent event) { toolkit.runSynchronized(new Runnable() { public void run() { StateData newEvent = new StateData( event.getState(), event.getTime(), event.getException()); Notification notification = toolkit.createNotification(STATE_CHANGE_NOTIF_TYPE); notification.setUserData(newEvent); toolkit.sendNotification(notification); lastNotification = notification; } }); } public Object invoke(RemoteOperation<?> operation, Object[] params) throws MBeanException, ReflectionException { if (SYNCHRONIZE.equals(operation)) { return new Notification[] { lastNotification }; } if (LAST_STATE_EVENT.equals(operation)) { return lastNotification.getUserData(); } throw new ReflectionException( new IllegalStateException("invoked for an unknown method."), operation.toString()); } public void destroy() { stateful.removeStateListener(this); } } public static class StateData implements Serializable { private static final long serialVersionUID = 2009063000L; private final State jobState; private final Date date; private final Throwable throwable; public StateData(State state, Date date, Throwable throwable) { this.jobState = state; this.date = date; if (throwable == null) { this.throwable = null; } else { this.throwable = new OddjobTransportableException(throwable); } } public State getJobState() { return jobState; } public Date getDate() { return date; } public Throwable getThrowable() { return throwable; } } }