001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.jmx.connector.invoker;
023:
024: import java.lang.reflect.InvocationTargetException;
025: import java.lang.reflect.Method;
026: import java.lang.reflect.UndeclaredThrowableException;
027: import java.rmi.RemoteException;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Map;
032: import java.security.Principal;
033: import javax.management.InstanceNotFoundException;
034: import javax.management.ListenerNotFoundException;
035: import javax.management.MBeanServer;
036: import javax.management.Notification;
037: import javax.management.NotificationFilter;
038: import javax.management.NotificationListener;
039: import javax.management.ObjectName;
040:
041: import org.jboss.invocation.Invocation;
042: import org.jboss.invocation.MarshalledInvocation;
043: import org.jboss.jmx.adaptor.rmi.RMINotificationListener;
044: import org.jboss.jmx.connector.invoker.client.InvokerAdaptorException;
045: import org.jboss.mx.server.ServerConstants;
046: import org.jboss.system.Registry;
047: import org.jboss.system.ServiceMBeanSupport;
048:
049: /**
050: * A JBoss service exposes an invoke(Invocation) operation that maps
051: * calls to the ExposedInterface onto the MBeanServer this service
052: * is registered with. It is used in conjunction with a proxy factory
053: * to expose the MBeanServer to remote clients through arbitrary
054: * protocols.<p>
055: *
056: * It sets up the correct classloader before unmarshalling the
057: * arguments, this relies on the ObjectName being seperate from
058: * from the other method arguments to avoid unmarshalling them
059: * before the classloader is determined from the ObjectName.<p>
060: *
061: * The interface is configurable, it must be similar to MBeanServer,
062: * though not necessarily derived from it<p>
063: *
064: * The invoker is configurable and must be specified
065: *
066: * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>
067: * @author Scott.Stark@jboss.org
068: * @version $Revision: 57209 $
069: *
070: * @jmx:mbean name="jboss.jmx:type=adaptor,protocol=INVOKER"
071: * extends="org.jboss.system.ServiceMBean"
072: **/
073: public class InvokerAdaptorService extends ServiceMBeanSupport
074: implements InvokerAdaptorServiceMBean, ServerConstants {
075: private ObjectName mbeanRegistry;
076: /** */
077: private Map marshalledInvocationMapping = new HashMap();
078: /** */
079: private Class[] exportedInterfaces;
080: /** A HashSet<Method> addNotificationListener methods */
081: private HashSet addNotificationListeners = new HashSet();
082: /** A HashSet<Method> removeNotificationListener methods */
083: private HashSet removeNotificationListeners = new HashSet();
084: /** A HashSet<RMINotificationListener, NotificationListenerDelegate> for the
085: registered listeners */
086: protected HashMap remoteListeners = new HashMap();
087:
088: public InvokerAdaptorService() {
089: }
090:
091: /**
092: * @jmx:managed-attribute
093: */
094: public Class[] getExportedInterfaces() {
095: return exportedInterfaces;
096: }
097:
098: /**
099: * @jmx:managed-attribute
100: */
101: public void setExportedInterfaces(Class[] exportedInterfaces) {
102: this .exportedInterfaces = exportedInterfaces;
103: }
104:
105: protected void startService() throws Exception {
106: mbeanRegistry = new ObjectName(MBEAN_REGISTRY);
107:
108: // Build the interface method map
109: HashMap tmpMap = new HashMap(61);
110: for (int n = 0; n < exportedInterfaces.length; n++) {
111: Class iface = exportedInterfaces[n];
112: Method[] methods = iface.getMethods();
113: for (int m = 0; m < methods.length; m++) {
114: Method method = methods[m];
115: Long hash = new Long(MarshalledInvocation
116: .calculateHash(method));
117: tmpMap.put(hash, method);
118: }
119: /* Look for a void addNotificationListener(ObjectName name,
120: RMINotificationListener listener, NotificationFilter filter,
121: Object handback)
122: */
123: try {
124: Class[] sig = { ObjectName.class,
125: RMINotificationListener.class,
126: NotificationFilter.class, Object.class };
127: Method addNotificationListener = iface.getMethod(
128: "addNotificationListener", sig);
129: addNotificationListeners.add(addNotificationListener);
130: } catch (Exception e) {
131: log
132: .debug(iface
133: + "No addNotificationListener(ObjectName, RMINotificationListener)");
134: }
135:
136: /* Look for a void removeNotificationListener(ObjectName,
137: RMINotificationListener)
138: */
139: try {
140: Class[] sig = { ObjectName.class,
141: RMINotificationListener.class };
142: Method removeNotificationListener = iface.getMethod(
143: "removeNotificationListener", sig);
144: removeNotificationListeners
145: .add(removeNotificationListener);
146: } catch (Exception e) {
147: log
148: .debug(iface
149: + "No removeNotificationListener(ObjectName, RMINotificationListener)");
150: }
151: }
152: marshalledInvocationMapping = Collections
153: .unmodifiableMap(tmpMap);
154:
155: // Place our ObjectName hash into the Registry so invokers can resolve it
156: Registry.bind(new Integer(serviceName.hashCode()), serviceName);
157: }
158:
159: protected void stopService() throws Exception {
160: // Remove the method hashses
161: if (exportedInterfaces != null) {
162: for (int n = 0; n < exportedInterfaces.length; n++)
163: MarshalledInvocation
164: .removeHashes(exportedInterfaces[n]);
165: }
166: marshalledInvocationMapping = null;
167: remoteListeners.clear();
168: Registry.unbind(new Integer(serviceName.hashCode()));
169: }
170:
171: /**
172: * Expose the service interface mapping as a read-only attribute
173: *
174: * @jmx:managed-attribute
175: *
176: * @return A Map<Long hash, Method> of the MBeanServer
177: */
178: public Map getMethodMap() {
179: return marshalledInvocationMapping;
180: }
181:
182: /**
183: * Expose the MBeanServer service via JMX to invokers.
184: *
185: * @jmx:managed-operation
186: *
187: * @param invocation A pointer to the invocation object
188: * @return Return value of method invocation.
189: *
190: * @throws Exception Failed to invoke method.
191: */
192: public Object invoke(Invocation invocation) throws Exception {
193: try {
194: // Make sure we have the correct classloader before unmarshalling
195: ClassLoader oldCL = SecurityActions.getContextClassLoader();
196:
197: ClassLoader newCL = null;
198: // Get the MBean this operation applies to
199: ObjectName objectName = (ObjectName) invocation
200: .getValue("JMX_OBJECT_NAME");
201: if (objectName != null) {
202: // Obtain the ClassLoader associated with the MBean deployment
203: newCL = (ClassLoader) server.invoke(mbeanRegistry,
204: "getValue", new Object[] { objectName,
205: CLASSLOADER }, new String[] {
206: ObjectName.class.getName(),
207: String.class.getName() });
208: }
209:
210: if (newCL != null && newCL != oldCL)
211: SecurityActions.setContextClassLoader(newCL);
212:
213: try {
214: // Set the method hash to Method mapping
215: if (invocation instanceof MarshalledInvocation) {
216: MarshalledInvocation mi = (MarshalledInvocation) invocation;
217: mi.setMethodMap(marshalledInvocationMapping);
218: }
219: // Invoke the MBeanServer method via reflection
220: Method method = invocation.getMethod();
221: Object[] args = invocation.getArguments();
222: Principal principal = invocation.getPrincipal();
223: Object credential = invocation.getCredential();
224: Object value = null;
225: // Associate the method
226: SecurityActions.pushSubjectContext(principal,
227: credential, null);
228:
229: try {
230: if (addNotificationListeners.contains(method)) {
231: ObjectName name = (ObjectName) args[0];
232: RMINotificationListener listener = (RMINotificationListener) args[1];
233: NotificationFilter filter = (NotificationFilter) args[2];
234: Object handback = args[3];
235: addNotificationListener(name, listener, filter,
236: handback);
237: } else if (removeNotificationListeners
238: .contains(method)) {
239: ObjectName name = (ObjectName) args[0];
240: RMINotificationListener listener = (RMINotificationListener) args[1];
241: removeNotificationListener(name, listener);
242: } else {
243: String name = method.getName();
244: Class[] paramTypes = method.getParameterTypes();
245: Method mbeanServerMethod = MBeanServer.class
246: .getMethod(name, paramTypes);
247: value = mbeanServerMethod.invoke(server, args);
248: }
249: } catch (InvocationTargetException e) {
250: Throwable t = e.getTargetException();
251: if (t instanceof Exception)
252: throw (Exception) t;
253: else
254: throw new UndeclaredThrowableException(t,
255: method.toString());
256: }
257:
258: return value;
259: } finally {
260: // Restore the input security context
261: SecurityActions.popSubjectContext();
262: // Restore the input class loader
263: if (newCL != null && newCL != oldCL)
264: SecurityActions.setContextClassLoader(oldCL);
265: }
266: } catch (Throwable t) {
267: throw new InvokerAdaptorException(t);
268: }
269: }
270:
271: public void addNotificationListener(ObjectName name,
272: RMINotificationListener listener,
273: NotificationFilter filter, Object handback)
274: throws InstanceNotFoundException, RemoteException {
275: if (log.isTraceEnabled())
276: log.trace("addNotificationListener, name=" + name
277: + ", listener=" + listener);
278: NotificationListenerDelegate delegate = new NotificationListenerDelegate(
279: listener, name);
280: remoteListeners.put(listener, delegate);
281: getServer().addNotificationListener(name, delegate, filter,
282: handback);
283: }
284:
285: public void removeNotificationListener(ObjectName name,
286: RMINotificationListener listener)
287: throws InstanceNotFoundException,
288: ListenerNotFoundException, RemoteException {
289: if (log.isTraceEnabled())
290: log.trace("removeNotificationListener, name=" + name
291: + ", listener=" + listener);
292: NotificationListenerDelegate delegate = (NotificationListenerDelegate) remoteListeners
293: .remove(listener);
294: if (delegate == null)
295: throw new ListenerNotFoundException("No listener matches: "
296: + listener);
297: getServer().removeNotificationListener(name, delegate);
298: }
299:
300: private class NotificationListenerDelegate implements
301: NotificationListener {
302: /** The remote client */
303: private RMINotificationListener client;
304: /** The mbean the client is monitoring */
305: private ObjectName targetName;
306:
307: public NotificationListenerDelegate(
308: RMINotificationListener client, ObjectName targetName) {
309: this .client = client;
310: this .targetName = targetName;
311: }
312:
313: public void handleNotification(Notification notification,
314: Object handback) {
315: try {
316: if (log.isTraceEnabled()) {
317: log.trace("Sending notification to client, event:"
318: + notification);
319: }
320: client.handleNotification(notification, handback);
321: } catch (Throwable t) {
322: log
323: .debug(
324: "Failed to notify client, unregistering listener",
325: t);
326: try {
327: removeNotificationListener(targetName, client);
328: } catch (Exception e) {
329: log.debug("Failed to unregister listener", e);
330: }
331: }
332: }
333: }
334:
335: }
|