package org.oddjob.jmx.handlers; import java.io.Serializable; import java.lang.reflect.UndeclaredThrowableException; 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.arooa.ArooaDescriptor; import org.oddjob.arooa.ArooaParseException; import org.oddjob.arooa.ConfigurationHandle; import org.oddjob.arooa.parsing.ArooaContext; import org.oddjob.arooa.parsing.ArooaElement; import org.oddjob.arooa.parsing.ConfigOwnerEvent; import org.oddjob.arooa.parsing.ConfigSessionEvent; import org.oddjob.arooa.parsing.ConfigurationOwner; import org.oddjob.arooa.parsing.ConfigurationOwnerSupport; import org.oddjob.arooa.parsing.ConfigurationSession; import org.oddjob.arooa.parsing.ConfigurationSessionSupport; import org.oddjob.arooa.parsing.CutAndPasteSupport; import org.oddjob.arooa.parsing.DragPoint; import org.oddjob.arooa.parsing.DragTransaction; import org.oddjob.arooa.parsing.OwnerStateListener; import org.oddjob.arooa.parsing.SerializableDesignFactory; import org.oddjob.arooa.parsing.SessionStateListener; import org.oddjob.arooa.registry.ChangeHow; import org.oddjob.arooa.xml.XMLArooaParser; import org.oddjob.arooa.xml.XMLConfiguration; import org.oddjob.jmx.RemoteOperation; import org.oddjob.jmx.client.ClientHandlerResolver; import org.oddjob.jmx.client.ClientInterfaceHandlerFactory; import org.oddjob.jmx.client.ClientSideToolkit; import org.oddjob.jmx.client.HandlerVersion; import org.oddjob.jmx.client.SimpleHandlerResolver; import org.oddjob.jmx.server.JMXOperationPlus; import org.oddjob.jmx.server.ServerInterfaceHandler; import org.oddjob.jmx.server.ServerInterfaceHandlerFactory; import org.oddjob.jmx.server.ServerSideToolkit; /** * * @author rob */ public class ComponentOwnerHandlerFactory implements ServerInterfaceHandlerFactory<ConfigurationOwner, ConfigurationOwner> { public static final HandlerVersion VERSION = new HandlerVersion(3, 0); public static final String MODIFIED_NOTIF_TYPE = "oddjob.config.modified"; public static final String CHANGE_NOTIF_TYPE = "oddjob.config.changed"; private static final JMXOperationPlus<Integer> SESSION_AVAILABLE = new JMXOperationPlus<Integer>( "ConfigurationOwner.sessionAvailable", "", Integer.class, MBeanOperationInfo.INFO ); private static final JMXOperationPlus<DragPointInfo> DRAG_POINT_INFO = new JMXOperationPlus<DragPointInfo>( "dragPointInfo", "", DragPointInfo.class, MBeanOperationInfo.INFO ).addParam("component", Object.class, ""); private static final JMXOperationPlus<Void> CUT = new JMXOperationPlus<Void>( "configCut", "", Void.TYPE, MBeanOperationInfo.ACTION_INFO ).addParam("component", Object.class, ""); private static final JMXOperationPlus<String> PASTE = new JMXOperationPlus<String>( "configPaste", "", String.class, MBeanOperationInfo.ACTION ).addParam( "component", Object.class, "").addParam( "index", Integer.TYPE, "").addParam( "config", String.class, ""); private static final JMXOperationPlus<Boolean> IS_MODIFIED = new JMXOperationPlus<Boolean>( "configIsModified", "", Boolean.class, MBeanOperationInfo.INFO); private static final JMXOperationPlus<String> SAVE = new JMXOperationPlus<String>( "configSave", "", String.class, MBeanOperationInfo.ACTION); private static final JMXOperationPlus<Void> REPLACE = new JMXOperationPlus<Void>( "configReplace", "", Void.TYPE, MBeanOperationInfo.INFO ).addParam("component", Object.class, ""); private static final JMXOperationPlus<ComponentOwnerInfo> INFO = new JMXOperationPlus<ComponentOwnerInfo>( "componentOwnerInfo", "Basic Info For Component Owner", ComponentOwnerInfo.class, MBeanOperationInfo.INFO ); /* * (non-Javadoc) * @see org.oddjob.jmx.server.ServerInterfaceHandlerFactory#interfaceClass() */ public Class<ConfigurationOwner> interfaceClass() { return ConfigurationOwner.class; } public MBeanAttributeInfo[] getMBeanAttributeInfo() { return new MBeanAttributeInfo[0]; } public MBeanOperationInfo[] getMBeanOperationInfo() { return new MBeanOperationInfo[] { INFO.getOpInfo(), SESSION_AVAILABLE.getOpInfo(), DRAG_POINT_INFO.getOpInfo(), CUT.getOpInfo(), PASTE.getOpInfo(), SAVE.getOpInfo(), IS_MODIFIED.getOpInfo(), REPLACE.getOpInfo() }; } public MBeanNotificationInfo[] getMBeanNotificationInfo() { MBeanNotificationInfo[] nInfo = new MBeanNotificationInfo[] { new MBeanNotificationInfo(new String[] { MODIFIED_NOTIF_TYPE }, Notification.class.getName(), "Modified Notification.")}; return nInfo; } public ServerInterfaceHandler createServerHandler( ConfigurationOwner target, ServerSideToolkit ojmb) { return new ServerComponentOwnerHandler(target, ojmb); } public ClientHandlerResolver<ConfigurationOwner> clientHandlerFactory() { return new SimpleHandlerResolver<ConfigurationOwner>( ClientConfigurationOwnerHandlerFactory.class.getName(), VERSION); } public static class ClientConfigurationOwnerHandlerFactory implements ClientInterfaceHandlerFactory<ConfigurationOwner> { public Class<ConfigurationOwner> interfaceClass() { return ConfigurationOwner.class; } public HandlerVersion getVersion() { return VERSION; } public ConfigurationOwner createClientHandler(ConfigurationOwner proxy, ClientSideToolkit toolkit) { return new ClientCompontOwnerHandler(proxy, toolkit); } } /** * The Client {@link ConfigurationOwner} */ static class ClientCompontOwnerHandler implements ConfigurationOwner { private final ClientSideToolkit clientToolkit; private final ConfigurationOwnerSupport ownerSupport; private final SerializableDesignFactory rootDesignFactory; private final ArooaElement rootElement; private volatile boolean listening; private final NotificationListener listener = new NotificationListener() { public void handleNotification(Notification notification, Object handback) { updateSession((ConfigOwnerEvent.Change) notification.getUserData()); }; }; ClientCompontOwnerHandler(ConfigurationOwner proxy, final ClientSideToolkit toolkit) { this.clientToolkit = toolkit; ownerSupport = new ConfigurationOwnerSupport(proxy); updateSession(null); ownerSupport.setOnFirst(new Runnable() { public void run() { updateSession(null); toolkit.registerNotificationListener( CHANGE_NOTIF_TYPE, listener); listening = true; } }); ownerSupport.setOnEmpty(new Runnable() { public void run() { listening = false; toolkit.removeNotificationListener( CHANGE_NOTIF_TYPE, listener); } }); try { ComponentOwnerInfo info = clientToolkit.invoke( INFO); rootDesignFactory = info.rootDesignFactory; rootElement = info.rootElement; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public ConfigurationSession provideConfigurationSession() { if (!listening) { updateSession(null); } return ownerSupport.provideConfigurationSession(); } /** * Lots of complicated logic to see if the server * configuration session has changed. * * @param change */ private void updateSession(ConfigOwnerEvent.Change change) { if (change == null || change == ConfigOwnerEvent.Change.SESSION_CREATED) { Integer newId = null; try { newId = clientToolkit.invoke(SESSION_AVAILABLE); } catch (InstanceNotFoundException e) { // Server Object no longer with us. newId = null; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } if (newId == null) { ownerSupport.setConfigurationSession(null); } else { ClientConfigurationSessionHandler existing = (ClientConfigurationSessionHandler) ownerSupport.provideConfigurationSession(); if (existing == null || existing.id != newId.intValue()) { ownerSupport.setConfigurationSession(null); ownerSupport.setConfigurationSession( new ClientConfigurationSessionHandler( clientToolkit, newId.intValue())); } } } else { ownerSupport.setConfigurationSession(null); } } public void addOwnerStateListener(OwnerStateListener listener) { ownerSupport.addOwnerStateListener(listener); } public void removeOwnerStateListener(OwnerStateListener listener) { ownerSupport.removeOwnerStateListener(listener); } @Override public SerializableDesignFactory rootDesignFactory() { return rootDesignFactory; } @Override public ArooaElement rootElement() { return rootElement; } } /** * The client {@link ConfigurationSession}. */ static class ClientConfigurationSessionHandler implements ConfigurationSession { private final ClientSideToolkit clientToolkit; private final ConfigurationSessionSupport sessionSupport; private final int id; private final NotificationListener listener = new NotificationListener() { public void handleNotification(Notification notification, Object handback) { Boolean modified = (Boolean) notification.getUserData(); if (modified) { sessionSupport.modified(); } else { sessionSupport.saved(); } } }; public ClientConfigurationSessionHandler(final ClientSideToolkit clientToolkit, int id) { this.id = id; this.clientToolkit = clientToolkit; sessionSupport = new ConfigurationSessionSupport(this); sessionSupport.setOnFirst(new Runnable() { public void run() { clientToolkit.registerNotificationListener(MODIFIED_NOTIF_TYPE, listener); } }); sessionSupport.setOnEmpty(new Runnable() { public void run() { clientToolkit.removeNotificationListener(MODIFIED_NOTIF_TYPE, listener); } }); } public DragPoint dragPointFor(Object component) { if (component == null) { throw new NullPointerException("No component."); } try { final DragPointInfo dragPointInfo = (DragPointInfo) clientToolkit.invoke( DRAG_POINT_INFO, new Object[] { component }); return createDragPoint(component, dragPointInfo); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public void save() throws ArooaParseException { try { clientToolkit.invoke( SAVE); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public boolean isModified() { try { return clientToolkit.invoke( IS_MODIFIED); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public void addSessionStateListener(SessionStateListener listener) { sessionSupport.addSessionStateListener(listener); } public void removeSessionStateListener(SessionStateListener listener) { sessionSupport.removeSessionStateListener(listener); } public ArooaDescriptor getArooaDescriptor() { return clientToolkit.getClientSession().getArooaSession().getArooaDescriptor(); } private DragPoint createDragPoint(final Object component, final DragPointInfo dragPointInfo) { if (dragPointInfo == null) { return null; } return new DragPoint() { public boolean supportsCut() { return dragPointInfo.supportsCut; } public boolean supportsPaste() { return dragPointInfo.supportsPaste; } public DragTransaction beginChange(ChangeHow how) { // Only create a fake client DragTransaction. The server will // create a real one. return new DragTransaction() { @Override public void rollback() { } @Override public void commit() { } }; } public String copy() { return dragPointInfo.copy; } public void cut() { try { clientToolkit.invoke( CUT, new Object[] { component }); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public ConfigurationHandle parse( ArooaContext parentContext) throws ArooaParseException { try { final XMLConfiguration config = new XMLConfiguration("Server Config", dragPointInfo.copy); final ConfigurationHandle handle = config.parse(parentContext); return new ConfigurationHandle() { public ArooaContext getDocumentContext() { return handle.getDocumentContext(); } public void save() throws ArooaParseException { config.setSaveHandler(new XMLConfiguration.SaveHandler() { @Override public void acceptXML(String xml) { try { if (xml.equals(dragPointInfo.copy)) { return; } clientToolkit.invoke( REPLACE, new Object[] { component, xml }); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }); handle.save(); } }; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } public void paste(int index, String config) throws ArooaParseException { try { clientToolkit.invoke( PASTE, new Object[] { component, index, config }); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }; } } class ServerComponentOwnerHandler implements ServerInterfaceHandler { private final ConfigurationOwner configurationOwner; private final ServerSideToolkit toolkit; private ConfigurationSession configurationSession; private final SessionStateListener modifiedListener = new SessionStateListener() { public void sessionModifed(ConfigSessionEvent event) { send(true); } public void sessionSaved(ConfigSessionEvent event) { send(false); } void send(final boolean modified) { toolkit.runSynchronized(new Runnable() { public void run() { Notification notification = toolkit.createNotification(MODIFIED_NOTIF_TYPE); notification.setUserData(new Boolean(modified)); toolkit.sendNotification(notification); } }); } }; private final OwnerStateListener configurationListener = new OwnerStateListener() { public void sessionChanged(final ConfigOwnerEvent event) { configurationSession = configurationOwner.provideConfigurationSession(); if (configurationSession != null) { configurationSession.addSessionStateListener(modifiedListener); } toolkit.runSynchronized(new Runnable() { public void run() { Notification notification = toolkit.createNotification(CHANGE_NOTIF_TYPE); notification.setUserData(event.getChange()); toolkit.sendNotification(notification); } }); } }; ServerComponentOwnerHandler(ConfigurationOwner configurationOwner, ServerSideToolkit serverToolkit) { this.configurationOwner = configurationOwner; this.toolkit = serverToolkit; configurationOwner.addOwnerStateListener(configurationListener); configurationSession = configurationOwner.provideConfigurationSession(); if (configurationSession != null) { configurationSession.addSessionStateListener(modifiedListener); } } public Object invoke(RemoteOperation<?> operation, Object[] params) throws MBeanException, ReflectionException { if (INFO.equals(operation)) { return new ComponentOwnerInfo(configurationOwner); } if (SESSION_AVAILABLE.equals(operation)) { if (configurationSession == null) { return null; } else { return new Integer(System.identityHashCode(configurationSession)); } } if (configurationSession == null) { throw new MBeanException(new IllegalStateException("No Config Session - Method " + operation + " should not have been called!")); } if (SAVE.equals(operation)) { try { configurationSession.save(); return null; } catch (ArooaParseException e) { throw new MBeanException(e); } } if (IS_MODIFIED.equals(operation)) { return configurationSession.isModified(); } DragPoint dragPoint = null; if (params != null && params.length > 0) { Object component = params[0]; dragPoint = configurationSession.dragPointFor(component); } if (DRAG_POINT_INFO.equals(operation)) { if (dragPoint == null) { return null; } else { return new DragPointInfo(dragPoint); } } if (dragPoint == null) { throw new MBeanException(new IllegalStateException("Null Drag Point - Method " + operation + " should not have been called!")); } if (CUT.equals(operation)) { DragTransaction trn = dragPoint.beginChange(ChangeHow.FRESH); dragPoint.cut(); try { trn.commit(); } catch (ArooaParseException e) { trn.rollback(); throw new MBeanException(e); } return null; } else if (PASTE.equals(operation)) { Integer index = (Integer) params[1]; String config = (String) params[2]; DragTransaction trn = dragPoint.beginChange(ChangeHow.FRESH); try { dragPoint.paste(index, config); trn.commit(); } catch (Exception e) { trn.rollback(); throw new MBeanException(e); } return null; } else if (REPLACE.equals(operation)) { String config = (String) params[1]; try { XMLArooaParser parser = new XMLArooaParser(); ConfigurationHandle handle = parser.parse(dragPoint); ArooaContext documentContext = handle.getDocumentContext(); CutAndPasteSupport.replace(documentContext.getParent(), documentContext, new XMLConfiguration("Edited Config", config)); handle.save(); } catch (ArooaParseException e) { throw new MBeanException(e); } return null; } else { throw new ReflectionException( new IllegalStateException("Invoked for an unknown method [" + operation.toString() + "]"), operation.toString()); } } public void destroy() { configurationOwner.removeOwnerStateListener(configurationListener); if (configurationSession != null) { configurationSession.removeSessionStateListener(modifiedListener); } } } } class DragPointInfo implements Serializable { private static final long serialVersionUID = 2009020400L; final boolean supportsCut; final boolean supportsPaste; final String copy; DragPointInfo(DragPoint serverDragPoint) { this.supportsCut = serverDragPoint.supportsCut(); this.supportsPaste = serverDragPoint.supportsPaste(); this.copy = serverDragPoint.copy(); } } class ComponentOwnerInfo implements Serializable { private static final long serialVersionUID = 2011090800L; final SerializableDesignFactory rootDesignFactory; final ArooaElement rootElement; ComponentOwnerInfo(ConfigurationOwner serverConfigOwner) { this.rootDesignFactory = serverConfigOwner.rootDesignFactory(); this.rootElement = serverConfigOwner.rootElement(); } }