/* * 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.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import javax.management.MBeanException; import javax.management.MBeanServer; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.ReflectionException; import org.jboss.logging.Logger; import org.jboss.remoting.Client; import org.jboss.remoting.ConnectionFailedException; import org.jboss.remoting.InvokerLocator; import org.jboss.remoting.Subsystem; import org.jboss.remoting.invocation.NameBasedInvocation; /** * MBeanServerClientInvokerProxy is an MBeanServer dynamic proxy that will forward all * MBeanServer requests to a remote MBeanServer via a RemoteClientInvoker. * * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a> * @version $Revision: 81023 $ */ public class MBeanServerClientInvokerProxy implements InvocationHandler { private static final Logger log = Logger.getLogger(MBeanServerClientInvokerProxy.class.getName()); private final String serverId; private final String localJmxId; private final transient InvokerLocator locator; private final transient Client client; private final transient Map paramMap = new HashMap(); private NotificationPoller poller = new NotificationPoller(); private Timer pollTimer = new Timer(true); private static transient Map proxies = new HashMap(); private transient Set listeners = new HashSet(); private MBeanServerClientInvokerProxy(ClassLoader cl, InvokerLocator invoker, String localJmxId, String id) throws Exception { this.localJmxId = localJmxId; this.serverId = id; if(this.serverId == null) { throw new IllegalArgumentException("MBeanServer ID not found - make sure the NetworkRegistry MBean is running"); } this.client = new Client(cl, invoker, Subsystem.JMX, null); this.locator = new InvokerLocator(invoker.getLocatorURI()); this.proxies.put(id, this); setupPollingTimer(); } /** * remove the proxy for a given JMX id * * @param id * @return */ public static synchronized MBeanServerClientInvokerProxy remove(String id) { return (MBeanServerClientInvokerProxy) proxies.remove(id); } /** * get a proxy for a given JMX Id * * @param id * @return */ public static synchronized MBeanServerClientInvokerProxy get(String id) { return (MBeanServerClientInvokerProxy) proxies.get(id); } /** * setup the polling based on the <tt>pollinterval</tt> locator attribute. <P> * <p/> * For example, to set the pollinterval to every 2.5 seconds, you would configure * the client invoker locator to be: * <p/> * <CODE><PRE> * soap://192.168.10.1/pollinterval=2500 * </PRE></CODE> * <p/> * The default interval if not specified is 1000, for every 1 second. You can * disable polling by setting the interval to <tt><=0</tt>. */ protected void setupPollingTimer() { Map map = this.locator.getParameters(); long delay = 1000; // default if(map != null) { String ds = (String) map.get("pollinterval"); if(ds != null) { delay = Integer.parseInt(ds); } } if(delay > 0) { this.pollTimer.scheduleAtFixedRate(poller, 2000, delay); } } /** * return the remote JMX id * * @return */ public synchronized String getServerId() { return this.serverId; } /** * return the invoker locator * * @return */ public InvokerLocator getLocator() { return locator; } public static synchronized MBeanServer create(InvokerLocator locator, String localJmxId, String jmxId) throws Exception { // always use the same one previously registered if possible MBeanServer mbeanserver = MBeanServerRegistry.getMBeanServerFor(locator); if(mbeanserver != null) { return mbeanserver; } ClassLoader cl = Thread.currentThread().getContextClassLoader(); if(cl == null) { cl = MBeanServerClientInvokerProxy.class.getClassLoader(); } MBeanServerClientInvokerProxy handler = new MBeanServerClientInvokerProxy(cl, locator, localJmxId, jmxId); mbeanserver = (MBeanServer) Proxy.newProxyInstance(MBeanServerClientInvokerProxy.class.getClassLoader(), new Class[]{MBeanServer.class}, handler); MBeanServerRegistry.register(mbeanserver, handler); log.debug("created MBeanServer proxy to remote mbeanserver at: " + locator + " for JMX Id: " + jmxId + " from our JMX id: " + localJmxId); return mbeanserver; } /** * called to destroy the proxy and the invoker */ public synchronized void destroy() { if(log.isTraceEnabled()) { log.trace("destroy called on: " + this + " for locator: " + locator + ", and serverid: " + serverId); } client.disconnect(); MBeanServerRegistry.unregister(this); cancelPoller(); removeListeners(); paramMap.clear(); proxies.remove(serverId); } /** * cancel the running poller */ private synchronized void cancelPoller() { if(poller != null) { poller.cancel(); poller = null; } if(pollTimer != null) { pollTimer.cancel(); pollTimer = null; } } private synchronized void removeListeners() { Iterator iter = listeners.iterator(); while(iter.hasNext()) { Object key = iter.next(); ClientListener.remove(key); iter.remove(); } } private void addNotificationListener(Method m, Object args[], String sig[], Map payload) throws Throwable { String methodName = m.getName(); Object key = ClientListener.register(serverId, (ObjectName) args[0], args[1], (NotificationFilter) args[2], args[3]); listeners.add(key); Object a[] = new Object[]{args[0], null, args[2], key}; // make sure we pass our local id as session id client.setSessionId(serverId); client.invoke(new NameBasedInvocation(methodName, a, sig), payload); } private void removeNotificationListener(Method m, Object args[], String sig[], Map payload) throws Throwable { String methodName = m.getName(); Object id = ClientListener.makeId(serverId, (ObjectName) args[0], args[1]); listeners.remove(id); ClientListener cl = ClientListener.remove(id); Object a[] = new Object[]{args[0], null, id}; if(cl != null) { // make sure we pass our local id as session id client.setSessionId(serverId); client.invoke(new NameBasedInvocation(methodName, a, new String[]{ObjectName.class.getName(), sig[1], Integer.class.getName()}), payload); } } private boolean proxyEquals(Object proxy) { return (proxy.getClass() == this.getClass() && ((MBeanServerClientInvokerProxy) proxy).serverId.equals(serverId)); } private Integer proxyHashcode() { return new Integer(client.hashCode() + serverId.hashCode()); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // handle Object.class methods, so we don't go across the wire if(method.getDeclaringClass() == Object.class) { if(methodName.equals("equals")) { return new Boolean(proxyEquals(args[0])); } else if(methodName.equals("hashCode")) { return proxyHashcode(); } else if(methodName.equals("toString")) { return "MBeanServerClientInvokerProxy [serverid:" + serverId + ",locator:" + client.getInvoker().getLocator() + "]"; } } String sig[] = getMethodSignature(method); Map payload = new HashMap(1); if(methodName.equals("addNotificationListener")) { addNotificationListener(method, args, sig, payload); return null; } else if(methodName.equals("removeNotificationListener")) { removeNotificationListener(method, args, sig, payload); return null; } Object value = null; try { // make sure we pass our local id as session id client.setSessionId(serverId); value = client.invoke(new NameBasedInvocation(methodName, args, sig), payload); } catch(Throwable throwable) { if(log.isTraceEnabled()) { log.trace("remote invocation failed for method: " + methodName + " to: " + serverId, throwable); } if(throwable instanceof ConnectionFailedException) { destroy(); } rethrowMBeanException(throwable); } finally { if(payload != null) { // if the payload isn't null NotificationQueue queue = (NotificationQueue) payload.get("notifications"); if(queue != null && queue.isEmpty() == false) { deliverNotifications(queue, false); } } } return value; } private void rethrowMBeanException(Throwable throwable) throws MBeanException, ReflectionException { if(throwable instanceof MBeanException) { throw (MBeanException) throwable; } else if(throwable instanceof ReflectionException) { throw (ReflectionException) throwable; } else { if(throwable instanceof UndeclaredThrowableException) { UndeclaredThrowableException ut = (UndeclaredThrowableException) throwable; if(ut instanceof Exception) { throw new MBeanException((Exception) ut.getUndeclaredThrowable(), ut.getMessage()); } else { throw new MBeanException(new Exception(ut.getUndeclaredThrowable().getMessage())); } } else { if(throwable instanceof Exception) { throw new MBeanException((Exception) throwable, throwable.getMessage()); } throw new MBeanException(new Exception(throwable.getMessage())); } } } public void deliverNotifications(NotificationQueue queue, boolean async) throws InterruptedException { if(async && poller != null) { // we're receiving async, kill the poller thread, no need for it cancelPoller(); } if(queue == null) { return; } Iterator iter = queue.iterator(); while(iter.hasNext()) { final NotificationEntry entry = (NotificationEntry) iter.next(); final ClientListener listener = ClientListener.get(entry.getHandBack()); if(listener != null) { if(listener.listener instanceof NotificationListener) { if(log.isTraceEnabled()) { log.trace("sending notification for entry: " + entry + " to: " + listener.listener); } try { ((NotificationListener) listener.listener).handleNotification(entry.getNotification(), listener.handback); } catch(Throwable ex) { log.error("Error sending notification: " + entry.getNotification() + " to listener: " + listener); } } else { //ObjectName l = (ObjectName)listener.listener; //TODO: implement log.error("called unimplemented addListener method", new Exception()); } } else { log.warn("couldn't find client listener for handback: " + entry.getHandBack() + "\nentry:" + entry + "\nqueue: " + queue + "\ndump: " + ClientListener.dump()); } } } public String[] getMethodSignature(Method method) { if(paramMap.containsKey(method)) { return (String[]) paramMap.get(method); } Class paramTypes[] = method.getParameterTypes(); String sig[] = (paramTypes == null) ? null : new String[paramTypes.length]; if(paramTypes != null) { for(int c = 0; c < sig.length; c++) { sig[c] = paramTypes[c].getName(); } } paramMap.put(method, sig); return sig; } /** * notification pooler is a timer task that will poll a remote server invoker for * notifications and re-dispatch them locally */ private final class NotificationPoller extends TimerTask { public void run() { try { // we only poll if we're connected, and we have active listeners // attached remotely if(client.isConnected() && ClientListener.hasListeners()) { Map payload = new HashMap(1); // transport the special method that will just return null, but will also return // the notification queue for the session in the payload client.setSessionId(serverId); Boolean continuePolling = (Boolean) client.invoke(new NameBasedInvocation("$GetNotifications$", new Object[]{}, new String[]{}), payload); NotificationQueue queue = (NotificationQueue) payload.get("notifications"); if(queue != null && queue.isEmpty() == false) { // we have notifications, deliver locally, deliverNotifications(queue, false); } if(continuePolling.booleanValue() == false) { cancelPoller(); } } } catch(ConnectionFailedException cnf) { // remove ourself destroy(); } catch(Throwable ex) { //FIXME - what to do? ex.printStackTrace(); } } } }