/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.jmx.connector.invoker;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.rmi.RemoteException;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.jmx.adaptor.rmi.RMINotificationListener;
import org.jboss.jmx.connector.invoker.client.InvokerAdaptorException;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContext;
import org.jboss.system.Registry;
import org.jboss.system.ServiceMBeanSupport;
/**
* A JBoss service exposes an invoke(Invocation) operation that maps
* calls to the ExposedInterface onto the MBeanServer this service
* is registered with. It is used in conjunction with a proxy factory
* to expose the MBeanServer to remote clients through arbitrary
* protocols.<p>
*
* It sets up the correct classloader before unmarshalling the
* arguments, this relies on the ObjectName being seperate from
* from the other method arguments to avoid unmarshalling them
* before the classloader is determined from the ObjectName.<p>
*
* The interface is configurable, it must be similar to MBeanServer,
* though not necessarily derived from it<p>
*
* The invoker is configurable and must be specified
*
* @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>
* @author Scott.Stark@jboss.org
* @version $Revision: 86262 $
*
* @jmx:mbean name="jboss.jmx:type=adaptor,protocol=INVOKER"
* extends="org.jboss.system.ServiceMBean"
**/
public class InvokerAdaptorService
extends ServiceMBeanSupport
implements InvokerAdaptorServiceMBean
{
private ObjectName mbeanRegistry;
/** */
private Map marshalledInvocationMapping = new HashMap();
/** */
private Class[] exportedInterfaces;
/** A HashSet<Method> addNotificationListener methods */
private HashSet addNotificationListeners = new HashSet();
/** A HashSet<Method> removeNotificationListener methods */
private HashSet removeNotificationListeners = new HashSet();
/** A HashSet<RMINotificationListener, NotificationListenerDelegate> for the
registered listeners */
protected HashMap remoteListeners = new HashMap();
public InvokerAdaptorService()
{
}
/**
* @jmx:managed-attribute
*/
public Class[] getExportedInterfaces()
{
return exportedInterfaces;
}
/**
* @jmx:managed-attribute
*/
public void setExportedInterfaces(Class[] exportedInterfaces)
{
this.exportedInterfaces = exportedInterfaces;
}
protected void startService()
throws Exception
{
// Build the interface method map
HashMap tmpMap = new HashMap(61);
for(int n = 0; n < exportedInterfaces.length; n ++)
{
Class iface = exportedInterfaces[n];
Method[] methods = iface.getMethods();
for(int m = 0; m < methods.length; m ++)
{
Method method = methods[m];
Long hash = new Long(MarshalledInvocation.calculateHash(method));
tmpMap.put(hash, method);
}
/* Look for a void addNotificationListener(ObjectName name,
RMINotificationListener listener, NotificationFilter filter,
Object handback)
*/
try
{
Class[] sig = {ObjectName.class, RMINotificationListener.class,
NotificationFilter.class, Object.class};
Method addNotificationListener = iface.getMethod(
"addNotificationListener", sig);
addNotificationListeners.add(addNotificationListener);
}
catch(Exception e)
{
log.debug(iface+"No addNotificationListener(ObjectName, RMINotificationListener)");
}
/* Look for a void removeNotificationListener(ObjectName,
RMINotificationListener)
*/
try
{
Class[] sig = {ObjectName.class, RMINotificationListener.class};
Method removeNotificationListener = iface.getMethod(
"removeNotificationListener", sig);
removeNotificationListeners.add(removeNotificationListener);
}
catch(Exception e)
{
log.debug(iface+"No removeNotificationListener(ObjectName, RMINotificationListener)");
}
}
marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
// Place our ObjectName hash into the Registry so invokers can resolve it
Registry.bind(new Integer(serviceName.hashCode()), serviceName);
}
protected void stopService()
throws Exception
{
// Remove the method hashses
if( exportedInterfaces != null )
{
for(int n = 0; n < exportedInterfaces.length; n ++)
MarshalledInvocation.removeHashes(exportedInterfaces[n]);
}
marshalledInvocationMapping = null;
remoteListeners.clear();
Registry.unbind(new Integer(serviceName.hashCode()));
}
/**
* Expose the service interface mapping as a read-only attribute
*
* @jmx:managed-attribute
*
* @return A Map<Long hash, Method> of the MBeanServer
*/
public Map getMethodMap()
{
return marshalledInvocationMapping;
}
/**
* Expose the MBeanServer service via JMX to invokers.
*
* @jmx:managed-operation
*
* @param invocation A pointer to the invocation object
* @return Return value of method invocation.
*
* @throws Exception Failed to invoke method.
*/
public Object invoke(Invocation invocation)
throws Exception
{
try
{
// Make sure we have the correct classloader before unmarshalling
ClassLoader oldCL = SecurityActions.getContextClassLoader();
ClassLoader newCL = null;
// Get the MBean this operation applies to
ObjectName objectName = (ObjectName) invocation.getValue("JMX_OBJECT_NAME");
if (objectName != null)
{
// Obtain the ClassLoader associated with the MBean deployment
newCL = server.getClassLoaderFor(objectName);
}
if (newCL != null && newCL != oldCL)
SecurityActions.setContextClassLoader(newCL);
try
{
// Set the method hash to Method mapping
if (invocation instanceof MarshalledInvocation)
{
MarshalledInvocation mi = (MarshalledInvocation) invocation;
mi.setMethodMap(marshalledInvocationMapping);
}
// Invoke the MBeanServer method via reflection
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
Principal principal = invocation.getPrincipal();
Object credential = invocation.getCredential();
Object value = null;
SecurityContext sc = SecurityActions.createSecurityContext(SecurityConstants.DEFAULT_APPLICATION_POLICY);
SecurityActions.setSecurityContext(sc);
// Associate the method
SecurityActions.pushSubjectContext(principal, credential, null);
try
{
if( addNotificationListeners.contains(method) )
{
ObjectName name = (ObjectName) args[0];
RMINotificationListener listener = (RMINotificationListener)
args[1];
NotificationFilter filter = (NotificationFilter) args[2];
Object handback = args[3];
addNotificationListener(name, listener, filter, handback);
}
else if( removeNotificationListeners.contains(method) )
{
ObjectName name = (ObjectName) args[0];
RMINotificationListener listener = (RMINotificationListener)
args[1];
removeNotificationListener(name, listener);
}
else
{
String name = method.getName();
Class[] paramTypes = method.getParameterTypes();
Method mbeanServerMethod = MBeanServer.class.getMethod(name,
paramTypes);
value = mbeanServerMethod.invoke(server, args);
}
}
catch(InvocationTargetException e)
{
Throwable t = e.getTargetException();
if( t instanceof Exception )
throw (Exception) t;
else
throw new UndeclaredThrowableException(t, method.toString());
}
return value;
}
finally
{
// Restore the input security context
SecurityActions.popSubjectContext();
SecurityActions.clearSecurityContext();
// Restore the input class loader
if (newCL != null && newCL != oldCL)
SecurityActions.setContextClassLoader(oldCL);
}
}
catch (Throwable t)
{
throw new InvokerAdaptorException(t);
}
}
public void addNotificationListener(ObjectName name,
RMINotificationListener listener, NotificationFilter filter,
Object handback)
throws InstanceNotFoundException, RemoteException
{
if( log.isTraceEnabled() )
log.trace("addNotificationListener, name="+name+", listener="+listener);
NotificationListenerDelegate delegate =
new NotificationListenerDelegate(listener, name);
remoteListeners.put(listener, delegate);
getServer().addNotificationListener(name, delegate, filter, handback);
}
public void removeNotificationListener(ObjectName name,
RMINotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException,
RemoteException
{
if( log.isTraceEnabled() )
log.trace("removeNotificationListener, name="+name+", listener="+listener);
NotificationListenerDelegate delegate = (NotificationListenerDelegate)
remoteListeners.remove(listener);
if( delegate == null )
throw new ListenerNotFoundException("No listener matches: "+listener);
getServer().removeNotificationListener(name, delegate);
}
private class NotificationListenerDelegate
implements NotificationListener
{
/** The remote client */
private RMINotificationListener client;
/** The mbean the client is monitoring */
private ObjectName targetName;
public NotificationListenerDelegate(RMINotificationListener client,
ObjectName targetName)
{
this.client = client;
this.targetName = targetName;
}
public void handleNotification(Notification notification,
Object handback)
{
try
{
if( log.isTraceEnabled() )
{
log.trace("Sending notification to client, event:"+notification);
}
client.handleNotification(notification, handback);
}
catch(Throwable t)
{
log.debug("Failed to notify client, unregistering listener", t);
try
{
removeNotificationListener(targetName, client);
}
catch(Exception e)
{
log.debug("Failed to unregister listener", e);
}
}
}
}
}