/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.mx.server;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.lang.reflect.Method;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.JMException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeErrorException;
import javax.management.RuntimeMBeanException;
import javax.management.RuntimeOperationsException;
import org.jboss.mx.server.Invocation;
import org.jboss.mx.server.InvocationException;
import org.jboss.mx.logging.Logger;
import org.jboss.mx.logging.SystemLogger;
/**
*
* @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
* @version $Revision: 1.3 $
*
*/
public abstract class AbstractMBeanInvoker
implements MBeanInvoker
{
// Attributes ----------------------------------------------------
/**
* The target object for this invoker.
*/
private Object resource = null;
/**
* The metadata describing this MBean.
*/
protected MBeanInfo info = null;
protected Map attributeContextMap = new HashMap();
protected Map operationContextMap = new HashMap();
protected Map constructorContextMap = new HashMap();
protected InvocationContext getMBeanInfoCtx = null;
protected InvocationContext preRegisterCtx = null;
protected InvocationContext postRegisterCtx = null;
protected InvocationContext preDeregisterCtx = null;
protected InvocationContext postDeregisterCtx = null;
// TODO: allow to config invoker specific logs
// : multitarget mbean for invoker + log?
protected Logger log = SystemLogger.getLogger(AbstractMBeanInvoker.class);
private boolean isDelayedRegistration = false;
// Constructors --------------------------------------------------
/**
* Constructs a new invoker.
*/
public AbstractMBeanInvoker()
{
}
/**
* Constructs a new invoker with a given target resource.
*/
public AbstractMBeanInvoker(Object resource)
{
this();
this.resource = resource;
}
// DynamicMBean implementation -----------------------------------
/**
* Invokes the target resource. The default invocation used by this invoker
* implement sends the invocation through a stack of interceptors before
* reaching the target method.
*
* @param operationName name of the target method
* @param args argumetns for the target method
* @param signature signature of the target method
*
* @throws MBeanExcpetion if the target method raised a hecked exception
* @throws ReflectionException if there was an error trying to resolve or
* invoke the target method
* @throws RuntimeMBeanException if the target method raised an unchecked
* exception
* @throws RuntimerErrorException if the target method raised an error
*/
public Object invoke(String operationName, Object[] args, String[] signature)
throws MBeanException, ReflectionException
{
// TODO: __JBOSSMX_INVOCATION
// get the server side invocation context
OperationKey key = new OperationKey(operationName, signature);
InvocationContext ctx = (InvocationContext)operationContextMap.get(key);
// if the server does not contain this context, we do not have the operation
if (ctx == null)
{
throw new ReflectionException(new IllegalArgumentException(
"Unable to find operation " + operationName +
getSignatureString(signature))
);
}
// create the invocation object
Invocation invocation = new Invocation();
// copy the server's invocation context to the invocation
invocation.addContext(ctx);
// set the invocation's entry point
invocation.setType(InvocationContext.OP_INVOKE);
// set the args
invocation.setArgs(args);
try
{
// the default invocation implementation will invoke each interceptor
// declared in the invocation context before invoking the target method
return invocation.invoke();
}
// Both interceptors and the invocation object propagate only one exception
// type, InvocationException, which wraps the underlying JMX exception
// (which in turn may wrap application exception, as per the JMX spec).
// Unwrap the outermost InvocationException layer here.
catch (InvocationException e)
{
if (e.getTargetException() instanceof MBeanException)
throw (MBeanException)e.getTargetException();
else if (e.getTargetException() instanceof ReflectionException)
throw (ReflectionException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeMBeanException)
throw (RuntimeMBeanException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeErrorException)
throw (RuntimeErrorException)e.getTargetException();
else
throw new RuntimeException(e.getTargetException().toString());
}
// any other throwable object that gets propagated back to the invoker
// indicates an error in the server or in the interceptor implementation.
catch (Throwable t)
{
throw new RuntimeOperationsException(new RuntimeException(
"Unhandled throwable propagated to the invoker by " +
invocation + ":" +t.toString())
);
// TODO: mark interceptors so we can track which interceptor fails
}
// TODO: should be fixed by adding invocation return value object
finally
{
ctx.setDescriptor(invocation.getDescriptor());
}
}
/**
* Returns an attribte value. The request for the value is forced through
* a set of interceptors before the value is returned.
*
* @param attribute attribute name
*
* @return attribute value
*
* @throws AttributeNotFoundException if the requested attribute is not
* part of the MBean's management interface
* @throws MBeanException if retrieving the attribute value causes an
* application exception
* @throws ReflectionException if there was an error trying to retrieve
* the attribute value
*/
public Object getAttribute(String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException
{
// TODO: __JBOSSMX_INVOCATION
// lookup the server side invocation context
InvocationContext ctx = (InvocationContext)attributeContextMap.get(attribute);
// if we don't have a server side invocation context for the attribute,
// it does not exist as far as we are concerned
if (ctx == null)
throw new AttributeNotFoundException("not found: " + attribute);
// create the invocation object
Invocation invocation = new Invocation();
// copy the server's invocation context to the invocation
invocation.addContext(ctx);
// indicate the invocation access point was getAttribute() method
invocation.setType(InvocationContext.OP_GETATTRIBUTE);
try
{
return invocation.invoke();
}
// Both interceptors and the invocation object propagate only one exception
// type, InvocationException, which wraps the underlying JMX exception
// (which in turn may wrap application exception, as per the JMX spec).
// Unwrap the outermost InvocationException layer here.
catch (InvocationException e)
{
if (e.getTargetException() instanceof AttributeNotFoundException)
throw (AttributeNotFoundException)e.getTargetException();
if (e.getTargetException() instanceof MBeanException)
throw (MBeanException)e.getTargetException();
else if (e.getTargetException() instanceof ReflectionException)
throw (ReflectionException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeMBeanException)
throw (RuntimeMBeanException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeErrorException)
throw (RuntimeErrorException)e.getTargetException();
else
throw new RuntimeException(e.getTargetException().toString());
}
// any other throwable object that gets propagated back to the invoker
// indicates an error in the server or in the interceptor implementation.
catch (Throwable t)
{
throw new RuntimeOperationsException(new RuntimeException(
"Unhandled throwable propagated to the invoker by " +
invocation + ":" +t.toString())
);
// TODO: mark interceptors so we can track which interceptor fails
}
// TODO: should be fixed by adding invocation return value object
finally
{
ctx.setDescriptor(invocation.getDescriptor());
}
}
/**
* Sets an attribute value. The operation is forced through a set of
* interceptors before the new value for the attribute is set.
*
* @param attribute new attribute value
*
* @throws AttributeNotFoundException if the requested attribute is not part
* of the MBean's management interface
* @throws InvalidAttributeValueException if the attribute contains a value
* not suitable for the attribute
* @throws MBeanException if setting the attribute value causes an application
* exception
* @throws ReflectionException if there was an error trying to set the
* attribute value.
*/
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
InvalidAttributeValueException, MBeanException, ReflectionException
{
// TODO: __JBOSSMX_INVOCATION
if (attribute == null)
throw new InvalidAttributeValueException("null attribute");
// lookup the server side invocation context
InvocationContext ctx = (InvocationContext)attributeContextMap.get(attribute.getName());
// if we don't have a server side invocation context for the attribute,
// it does not exist as far as we are concerned
if (ctx == null)
throw new AttributeNotFoundException("not found: " + attribute.getName());
// create the invocation object
Invocation invocation = new Invocation();
// copy the server context to the invocation
invocation.addContext(ctx);
// indicate the access point as setAttribute()
invocation.setType(InvocationContext.OP_SETATTRIBUTE);
// set the attribute value as the argument
invocation.setArgs(new Object[] { attribute.getValue() });
try
{
// the default invocation implementation will invoke each interceptor
// declared in the invocation context before invoking the target method
invocation.invoke();
}
// Both interceptors and the invocation object propagate only one exception
// type, InvocationException, which wraps the underlying JMX exception
// (which in turn may wrap application exception, as per the JMX spec).
// Unwrap the outermost InvocationException layer here.
catch (InvocationException e)
{
if (e.getTargetException() instanceof InvalidAttributeValueException)
throw (InvalidAttributeValueException)e.getTargetException();
if (e.getTargetException() instanceof AttributeNotFoundException)
throw (AttributeNotFoundException)e.getTargetException();
if (e.getTargetException() instanceof MBeanException)
throw (MBeanException)e.getTargetException();
else if (e.getTargetException() instanceof ReflectionException)
throw (ReflectionException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeMBeanException)
throw (RuntimeMBeanException)e.getTargetException();
else if (e.getTargetException() instanceof RuntimeErrorException)
throw (RuntimeErrorException)e.getTargetException();
else
throw new RuntimeException(e.getTargetException().toString());
}
// any other throwable object that gets propagated back to the invoker
// indicates an error in the server or in the interceptor implementation.
catch (Throwable t)
{
throw new RuntimeOperationsException(new RuntimeException(
"Unhandled throwable propagated to the invoker by " +
invocation + ":" +t.toString())
);
}
// TODO: should be fixed by adding invocation return value object
finally
{
ctx.setDescriptor(invocation.getDescriptor());
}
}
public MBeanInfo getMBeanInfo()
{
// create the invocation object
Invocation invocation = new Invocation(getMBeanInfoCtx);
// set the invocation's access point as getMBeanInfo()
invocation.setType(InvocationContext.OP_GETMBEANINFO);
try
{
MBeanInfo info = (MBeanInfo)invocation.invoke();
return info;
}
catch (InvocationException e)
{
throw new RuntimeOperationsException(new RuntimeException(
"Unhandled throwable propagated to the invoker by " +
invocation + ":" + e.toString())
);
}
}
public AttributeList getAttributes(java.lang.String[] attributes)
{
if (attributes == null)
throw new IllegalArgumentException("null array");
AttributeList list = new AttributeList();
for (int i = 0; i < attributes.length; ++i)
{
try
{
list.add(new Attribute(attributes[i], getAttribute(attributes[i])));
}
catch (JMException ignored)
{
// if the attribute could not be retrieved, skip it
}
}
return list;
}
public AttributeList setAttributes(AttributeList attributes)
{
if (attributes == null)
throw new IllegalArgumentException("null list");
AttributeList results = new AttributeList();
Iterator it = attributes.iterator();
while (it.hasNext())
{
try
{
Attribute attr = (Attribute)it.next();
setAttribute(attr);
results.add(attr);
}
catch (JMException ignored)
{
// if unable to set the attribute, skip it
}
}
return results;
}
// MBeanRegistration implementation ------------------------------
/**
* Initializes this invoker. At the registration time we can be sure that
* all of the metadata is available and initialize the invoker and cache
* the data accordingly. <p>
*
* Subclasses that override the <tt>preRegister</tt> method must make sure
* they call <tt>super.preRegister()</tt> in their implementation to
* ensure proper initialization of the invoker.
*/
public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception
{
initAttributeContexts(info.getAttributes());
initOperationContexts(info.getOperations());
initDispatchers();
if (!isDelayedRegistration)
return invokePreRegister(server, name);
else
return name;
}
/**
*/
public void postRegister(Boolean registrationSuccessful)
{
if (!isDelayedRegistration)
invokePostRegister(registrationSuccessful);
}
/**
*/
public void preDeregister() throws Exception
{
if (!isDelayedRegistration)
invokePreDeregister();
}
/**
*/
public void postDeregister()
{
if (!isDelayedRegistration)
invokePostDeregister();
}
// NotificationEmitter implementation ------------------------
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter, Object handback)
{
addNotificationListenerToResource(listener, filter, handback);
}
protected void addNotificationListenerToResource(
NotificationListener listener, NotificationFilter filter, Object handback
)
{
if (resource instanceof NotificationBroadcaster)
{
((NotificationBroadcaster)resource).addNotificationListener(
listener, filter, handback
);
}
else
{
throw new RuntimeMBeanException(new IllegalArgumentException(
"Target XXX is not a notification broadcaster"
// FIXME: add the XXX object name, store from registration
));
}
}
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException
{
removeNotificationListenerFromResource(listener);
}
protected void removeNotificationListenerFromResource(NotificationListener listener)
throws ListenerNotFoundException
{
if (resource instanceof NotificationBroadcaster)
{
((NotificationBroadcaster)resource).removeNotificationListener(
listener
);
}
else
{
throw new RuntimeMBeanException(new IllegalArgumentException(
"Target XXX is not a notification broadcaster"
// FIXME: add the XXX object name, store from registration
));
}
}
public void removeNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws ListenerNotFoundException
{
removeNotificationListenerFromResource(listener, filter, handback);
}
protected void removeNotificationListenerFromResource(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws ListenerNotFoundException
{
if (resource instanceof NotificationEmitter)
{
((NotificationEmitter)resource).removeNotificationListener(
listener, filter, handback
);
}
else
{
throw new RuntimeMBeanException(new IllegalArgumentException(
"Target XXX is not a notification emitter"
// FIXME: add the XXX object name, store from registration
));
}
}
public MBeanNotificationInfo[] getNotificationInfo()
{
return getNotificationInfoFromResource();
}
protected MBeanNotificationInfo[] getNotificationInfoFromResource()
{
if (resource instanceof NotificationBroadcaster)
{
return ((NotificationBroadcaster)resource).getNotificationInfo();
}
else
return new MBeanNotificationInfo[] {};
}
// MBeanInvoker implementation -----------------------------------
public Object getResource()
{
return resource;
}
public void setResource(Object resource)
{
this.resource = resource;
}
public MBeanInfo getMetaData()
{
return info;
}
public void suspend() {}
public void suspend(long wait) throws TimeoutException {}
public void suspend(boolean force) {}
public boolean isSuspended() { return false; }
public void setInvocationTimeout(long time) {}
public long getInvocationTimeout() { return 0l; }
public void resume() {}
// Protected -----------------------------------------------------
protected void setDelayedRegistration(boolean b)
{
this.isDelayedRegistration = b;
}
protected boolean isDelayedRegistration()
{
return isDelayedRegistration;
}
protected ObjectName invokePreRegister(MBeanServer server, ObjectName name)
throws Exception
{
if (resource instanceof MBeanRegistration)
return ((MBeanRegistration)resource).preRegister(server, name);
return name;
}
protected void invokePostRegister(Boolean b)
{
if (resource instanceof MBeanRegistration)
((MBeanRegistration)resource).postRegister(b);
}
protected void invokePreDeregister() throws Exception
{
if (resource instanceof MBeanRegistration)
((MBeanRegistration)resource).preDeregister();
}
protected void invokePostDeregister()
{
if (resource instanceof MBeanRegistration)
((MBeanRegistration)resource).postDeregister();
}
protected void initAttributeContexts(MBeanAttributeInfo[] attributes)
{
// create invocation contexts for attributes
for (int i = 0; i < attributes.length; ++i)
{
InvocationContext ctx = new InvocationContext();
// fill in some default values, the attribute name
ctx.setName(attributes[i].getName());
ctx.setAttributeType(attributes[i].getType());
// set myself as the invoker
ctx.setInvoker(this);
//ctx.add(InvocationContext.ATTRIBUTE_ACCESS, getAccessCode(attributes[i]));
// store
attributeContextMap.put(attributes[i].getName(), ctx);
}
}
protected void initOperationContexts(MBeanOperationInfo[] operations)
{
// create invocation contexts for operations
for (int i = 0; i < operations.length; ++i)
{
InvocationContext ctx = new InvocationContext();
// extract operation name + signature
String opName = operations[i].getName();
MBeanParameterInfo[] signature = operations[i].getSignature();
// name is unchanged, fill in the context
ctx.setName(opName);
// signature doesn't change..
ctx.setSignature(signature);
// set myself as the invoker
ctx.setInvoker(this);
// add impact as part of ctx map (rarely accessed information)
//ctx.add(InvocationContext.OPERATION_IMPACT, operations[i].getImpact());
// create an operation key consisting of the name + signature
// (required for overloaded operations)
OperationKey opKey = new OperationKey(opName, signature);
// store
operationContextMap.put(opKey, ctx);
}
}
protected void initDispatchers()
{
MBeanOperationInfo[] operations = info.getOperations();
MethodMapper mmap = new MethodMapper(resource.getClass());
for (int i = 0; i < operations.length; ++i)
{
OperationKey opKey = new OperationKey(operations[i].getName(), operations[i].getSignature());
InvocationContext ctx = (InvocationContext)operationContextMap.get(opKey);
Method m = mmap.lookupOperation(operations[i]);
ctx.setDispatcher(new ReflectedDispatcher(resource, m));
// TODO: should setResource be part of the dispatcher?
ctx.setResource(resource);
}
getMBeanInfoCtx = new InvocationContext();
getMBeanInfoCtx.setInvoker(this);
}
protected String getSignatureString(String[] signature)
{
if (signature == null)
return "()";
if (signature.length == 0)
return "()";
StringBuffer sbuf = new StringBuffer(512);
sbuf.append("(");
for (int i = 0; i < signature.length - 1; ++i)
{
sbuf.append(signature[i]);
sbuf.append(",");
}
sbuf.append(signature[signature.length - 1]);
sbuf.append(")");
return sbuf.toString();
}
// Inner classes -------------------------------------------------
protected final class OperationKey
{
String[] keys = null;
int hash = 0;
public OperationKey(final String name, final String[] signature)
{
if (signature != null)
{
keys = new String[signature.length + 1];
keys[0] = name;
System.arraycopy(signature, 0, keys, 1, signature.length);
hash = name.hashCode();
}
else
{
keys = new String[] { name };
hash = name.hashCode();
}
}
public OperationKey(String name, MBeanParameterInfo[] signature)
{
if (signature == null)
signature = new MBeanParameterInfo[0];
keys = new String[signature.length + 1];
keys[0] = name;
for (int i = 0; i < signature.length; ++i)
{
keys[i + 1] = signature[i].getType();
}
hash = name.hashCode();
}
public OperationKey(MBeanOperationInfo info)
{
this(info.getName(), info.getSignature());
}
public int hashCode()
{
return hash;
}
public boolean equals(Object o)
{
OperationKey target = (OperationKey)o;
if (target.keys.length != keys.length)
return false;
for (int i = 0; i < keys.length; ++i)
{
if (!(keys[i].equals(target.keys[i])))
return false;
}
return true;
}
}
}