/* * 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.mx.remoting; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.util.Map; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.MBeanException; import javax.management.MBeanServer; import javax.management.NotificationFilter; import javax.management.ObjectName; import org.jboss.logging.Logger; import org.jboss.remoting.InvocationRequest; import org.jboss.remoting.callback.InvokerCallbackHandler; import org.jboss.remoting.InvokerLocator; import org.jboss.remoting.ServerInvocationHandler; import org.jboss.remoting.ServerInvoker; import org.jboss.remoting.ident.Identity; import org.jboss.remoting.invocation.NameBasedInvocation; /** * JMXSubsystemInvocationHandler is a ServerInvocationHandler that will forward requests to the * MBeanServer and return the results from the MBeanServer. * * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a> * @version $Revision: 81023 $ */ public class JMXSubsystemInvocationHandler implements ServerInvocationHandler { private static final Logger log = Logger.getLogger(JMXSubsystemInvocationHandler.class); private MBeanServer server; private MBeanNotificationCache notificationCache; private ServerInvoker invoker; private Identity identity; private static Method getObjectInstance; private static Method isRegistered; private static Method getAttribute; private static Method getAttributes; private static Method setAttribute; private static Method setAttributes; private static Method invoke; private static Method getMBeanInfo; static { try { Class LObject = (new Object[0]).getClass(); Class LString = (new String[0]).getClass(); Class[] Sig_ObjectName = new Class[]{ObjectName.class}; Class[] Sig_ObjectName_String = new Class[]{ObjectName.class, String.class}; Class[] Sig_ObjectName_LString = new Class[]{ObjectName.class, LString}; Class[] Sig_ObjectName_Attribute = new Class[]{ObjectName.class, Attribute.class}; Class[] Sig_ObjectName_AttributeList = new Class[]{ObjectName.class, AttributeList.class}; Class[] Sig_ObjectName_String_LObject_LString = new Class[]{ObjectName.class, String.class, LObject, LString}; getObjectInstance = MBeanServer.class.getMethod("getObjectInstance", Sig_ObjectName); isRegistered = MBeanServer.class.getMethod("isRegistered", Sig_ObjectName); getAttribute = MBeanServer.class.getMethod("getAttribute", Sig_ObjectName_String); getAttributes = MBeanServer.class.getMethod("getAttributes", Sig_ObjectName_LString); setAttribute = MBeanServer.class.getMethod("setAttribute", Sig_ObjectName_Attribute); setAttributes = MBeanServer.class.getMethod("setAttributes", Sig_ObjectName_AttributeList); invoke = MBeanServer.class.getMethod("invoke", Sig_ObjectName_String_LObject_LString); getMBeanInfo = MBeanServer.class.getMethod("getMBeanInfo", Sig_ObjectName); } catch(Exception e) { throw new RuntimeException("Error resolving methods", e); } } public JMXSubsystemInvocationHandler() { super(); } /** * set the invoker that owns this handler * * @param invoker */ public void setInvoker(ServerInvoker invoker) { this.invoker = invoker; } /** * set the mbean server that the handler can reference * * @param server */ public void setMBeanServer(MBeanServer server) { this.server = server; identity = Identity.get(server); // make sure our local server is set MBeanTransportPreference.setLocalServer(server, identity); if(log.isTraceEnabled()) { log.trace("setMBeanServer called with: " + server + " with identity: " + identity); } } /** * method is called to destroy the handler and remove all pending notifications and listeners * from the notification cache */ public synchronized void destroy() { if(notificationCache != null) { notificationCache.destroy(); notificationCache = null; } } protected void finalize() throws Throwable { destroy(); super.finalize(); } /** * pull any pending notifications from the queue and place in the return payload * * @param sessionId * @param payload */ private void storeNotifications(String sessionId, Map payload) { NotificationQueue q = (notificationCache == null) ? null : notificationCache.getNotifications(sessionId); if(q != null) { payload.put("notifications", q); } } public Object invoke(InvocationRequest invocation) throws Throwable { if(this.server == null) { throw new IllegalStateException("invoke called prior to mbean server being set"); } try { NameBasedInvocation nbi = (NameBasedInvocation) invocation.getParameter(); String methodName = nbi.getMethodName(); Object args [] = nbi.getParameters(); String signature [] = nbi.getSignature(); String sessionId = invocation.getSessionId(); // this method is called by a polling client for notifications if(methodName.equals("$GetNotifications$")) //FIXME- JGH: make this a little better { // if (notificationCache!=null && notificationCache.isConnectedBidirectionally(invocation.getClientLocator())) // { // return new Boolean(false); // } // just return, since the finally will automatically stick the queue in the // return payload for us. return new Boolean(true); } if(methodName.equals("$NOTIFICATIONS$")) //FIXME- JGH: make this a little better { // we are receiving async notifications from a remote server NotificationQueue queue = (NotificationQueue) args[0]; MBeanServerClientInvokerProxy p = MBeanServerClientInvokerProxy.get(queue.getSessionID()); if(p != null) { if(log.isTraceEnabled()) { log.trace("received remote notifications for JMX id: " + queue.getSessionID() + ", queue: " + queue); } p.deliverNotifications(queue, true); } else { log.warn("couldn't find a client invoker proxy for mbean serverid: " + queue.getSessionID() + ", dropping notifications [" + queue + "]"); } return null; } // add and remove are special cases, handle those accordingly if(methodName.equals("addNotificationListener") && signature.length == 4) { // listener field is always null, since we don't send it across handleAddNotificationListener(invocation.getLocator(), sessionId, (ObjectName) args[0], (NotificationFilter) args[2], args[3]); return null; } else if(methodName.equals("removeNotificationListener") && signature.length == 3) { // listener field is always null, since we don't send it across handleRemoveNotificationListener(invocation.getLocator(), sessionId, (ObjectName) args[0], args[2]); return null; } Object _args[] = (args == null && signature != null) ? new Object[signature.length] : args; // get the mbean server method that's being invoked Method method = getMethod(methodName, signature); // transport against the mbean server return method.invoke(server, _args); } catch(Throwable ex) { if(ex instanceof UndeclaredThrowableException) { UndeclaredThrowableException ut = (UndeclaredThrowableException) ex; Throwable ute = ut.getUndeclaredThrowable(); if(ute instanceof Exception) { throw new MBeanException((Exception) ute, ut.getUndeclaredThrowable().getMessage()); } else { throw new MBeanException(new Exception(ute.getMessage()), ute.getMessage()); } } if(ex instanceof InvocationTargetException) { throw ((InvocationTargetException) ex).getTargetException(); } throw ex; } finally { // on each invocation, we go ahead and deliver back // and pending notifications for this session to the remote // end if(notificationCache != null) { storeNotifications(invocation.getSessionId(), invocation.getReturnPayload()); } } } private synchronized void handleAddNotificationListener(InvokerLocator locator, String sessionId, ObjectName objName, NotificationFilter filter, Object handback) throws Throwable { if(notificationCache == null) { notificationCache = new MBeanNotificationCache(invoker, server); } notificationCache.addNotificationListener(locator, sessionId, objName, filter, handback); } private synchronized void handleRemoveNotificationListener(InvokerLocator locator, String sessionId, ObjectName objName, Object key) throws Throwable { if(notificationCache == null) { return; } notificationCache.removeNotificationListener(locator, sessionId, objName, key); } /** * convenience method to lookup the Method object for a given method and signature * * @param methodName * @param sig * @return * @throws java.lang.Throwable */ private Method getMethod(String methodName, String sig[]) throws Throwable { if(methodName.equals("invoke")) { return invoke; } else if(methodName.equals("getAttribute")) { return getAttribute; } else if(methodName.equals("setAttribute")) { return setAttribute; } else if(methodName.equals("getAttributes")) { return getAttributes; } else if(methodName.equals("setAttributes")) { return setAttributes; } else if(methodName.equals("setAttributes")) { return setAttributes; } else if(methodName.equals("getMBeanInfo")) { return getMBeanInfo; } else if(methodName.equals("getObjectInstance")) { return getObjectInstance; } else if(methodName.equals("isRegistered")) { return isRegistered; } Class[] params = null; if(sig != null) { params = new Class[sig.length]; for(int i = 0; i < sig.length; ++i) { params[i] = Class.forName(sig[i]); } } return MBeanServer.class.getMethod(methodName, params); } //NOTE: These were added as part of the new remoting callback, // but not yet implemented (need to compile). JMX remoting should // still work using the old way. -TME public void addListener(InvokerCallbackHandler callbackHandler) { //TODO: Need to implement -TME } public void removeListener(InvokerCallbackHandler callbackHandler) { //TODO: Need to implement -TME } }