package org.oddjob.jmx.server;
import java.io.NotSerializableException;
import java.rmi.RemoteException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.DynamicMBean;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.apache.log4j.Logger;
import org.oddjob.jmx.RemoteOddjobBean;
import org.oddjob.jmx.Utils;
/**
* A MBean which wraps an object providing an Oddjob management interface to the
* object.
* <p>
* Once the bean is created it will sit and wait for clients to interrogate it. When
* a client accesses the bean it should call the resync method which will cause the
* bean to resend the notifications necessary to recreate in the client, the state
* of the bean. During the resync the InterfaceHandlers should block any any more
* changes until the resync has completed.
*
*
* @author Rob Gordon.
*/
public class OddjobMBean extends NotificationBroadcasterSupport implements
DynamicMBean {
private static final Logger logger = Logger.getLogger(OddjobMBean.class);
/** The server node this object represents. */
private final Object node;
/** The server context for this OddjobMBean. */
private final ServerContext srvcon;
/** The notification sequenceNumber */
private int sequenceNumber = 0;
private final ObjectName objectName;
/** The factory for adding and removing beans. */
private final ServerSession factory;
/** The interface manager. */
private final ServerInterfaceManager serverInterfaceManager;
/** Used to ensure that no fresh notificatons are sent during a resync. */
private final Object resyncLock = new Object();
/**
* Constructor.
*
* @param node The job this is shadowing.
* @param objectName The objectName for this node.
* @param factory The factory for creating child OddjobMBeans. May be null only
* if this MBean will never have children.
* @param srvcon The server context The server context. Must not be null.
*
* @throws RemoteException
*/
public OddjobMBean(Object node, ObjectName objectName,
ServerSession factory, ServerContext srvcon) {
if (node == null) {
throw new NullPointerException("Component must not be null");
}
if (objectName == null) {
throw new NullPointerException("Object Name must not be null");
}
if (srvcon == null) {
throw new NullPointerException("Server Context must not be null");
}
this.node = node;
this.factory = factory;
this.srvcon = srvcon;
this.objectName = objectName;
ServerInterfaceManagerFactory imf =
srvcon.getModel().getInterfaceManagerFactory();
serverInterfaceManager = imf.create(node, new Toolkit());
}
public Object getNode() {
return node;
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#getAttribute(java.lang.String)
*/
public Object getAttribute(String attribute)
throws ReflectionException, MBeanException {
logger.debug("getAttribute(" + attribute + ")");
return invoke("get", new Object[] { attribute },
new String[] { String.class.getName() });
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute)
*/
public void setAttribute(Attribute attribute)
throws ReflectionException, MBeanException {
logger.debug("setAttribute(" + attribute.getName() + ")");
invoke("set", new Object[] { attribute.getClass(), attribute.getValue() },
new String[] { String.class.getName(), Object.class.getName() });
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#getAttributes(java.lang.String[])
*/
public AttributeList getAttributes(String[] attributes) {
AttributeList al = new AttributeList();
for (int i = 0; i < attributes.length; ++i) {
String attribute = attributes[i];
Attribute attr;
try {
attr = new Attribute(attribute, getAttribute(attribute));
al.add(attr);
} catch (ReflectionException e) {
logger.debug(e);
} catch (MBeanException e) {
logger.debug(e);
}
}
return al;
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList)
*/
public AttributeList setAttributes(AttributeList attributes) {
AttributeList al = new AttributeList();
for (Attribute attribute : attributes.asList()) {
try {
setAttribute(attribute);
al.add(attribute);
} catch (ReflectionException e) {
logger.debug(e);
} catch (MBeanException e) {
logger.debug(e);
}
}
return al;
}
/**
* Utility function to build a method name from the invoke method arguments.
*
* @return A String method description.
*/
static String methodDescription(final String actionName, String[] signature) {
// build an incredibly converluted debug message.
StringBuffer buf = new StringBuffer();
buf.append(actionName);
buf.append('(');
for (int j = 0; j < signature.length; ++j) {
buf.append(j== 0 ? "" : ", ");
buf.append(signature[j]);
}
buf.append(")");
return buf.toString();
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#invoke(java.lang.String, java.lang.Object[], java.lang.String[])
*/
public Object invoke(final String actionName, final Object[] params, String[] signature)
throws MBeanException, ReflectionException {
if (logger.isDebugEnabled()) {
String methodDescription = methodDescription(actionName, signature);
logger.debug("Invoking [" +
methodDescription + "] on [" + node + "]");
}
////////////////////////////////////////////////////////////
// anything else - pass to the interface manager.
Object[] imported = Utils.importResolve(params, factory);
if (imported == null) {
// ensure null params is converted to 0 length array.
imported = new Object[0];
}
Object result = serverInterfaceManager.invoke(actionName, imported, signature);
try {
return Utils.export(result);
} catch (NotSerializableException e) {
throw new MBeanException(e, "Failed to return result from [" + actionName + "(...)]");
}
}
/*
* (non-Javadoc)
* @see javax.management.DynamicMBean#getMBeanInfo()
*/
public MBeanInfo getMBeanInfo() {
return serverInterfaceManager.getMBeanInfo();
}
/*
* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#getNotificationInfo()
*/
public MBeanNotificationInfo[] getNotificationInfo() {
return serverInterfaceManager.getMBeanInfo().getNotifications();
}
/**
* Destroy this node. Notify all remote listeners their peer is dead.
*/
public void destroy() {
logger.debug("Destroying [" + this + "]");
serverInterfaceManager.destroy();
}
class Remote implements RemoteOddjobBean {
/**
* Get the component info.
*
* @return ServerInfo for the component.
*/
public ServerInfo serverInfo() {
return new ServerInfo(
srvcon.getAddress(),
serverInterfaceManager.allClientInfo());
}
public void noop() {
}
}
class Toolkit implements ServerSideToolkit {
public void sendNotification(Notification notification) {
OddjobMBean.this.sendNotification(notification);
}
public Notification createNotification(String type) {
synchronized(resyncLock) {
return new Notification(type, objectName, sequenceNumber++);
}
}
/**
* Used by handlers to execute functionallity while
* holding the resync lock.
*
* @param runnable The functionality to run.
*/
public void runSynchronized(Runnable runnable) {
synchronized (resyncLock) {
runnable.run();
}
}
/**
* Gives handlers access to the server context.
*
* @return The server context for this MBean.
*/
public ServerContext getContext() {
return srvcon;
}
public RemoteOddjobBean getRemoteBean() {
return new Remote();
}
public ServerSession getServerSession() {
return factory;
}
}
@Override
public String toString() {
return getClass().getSimpleName() + " for [" + node +
"], name " + objectName;
}
}