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.mx.util;
023:
024: import java.io.Serializable;
025:
026: import java.lang.reflect.InvocationHandler;
027: import java.lang.reflect.Method;
028: import java.lang.reflect.Proxy;
029:
030: import java.util.HashMap;
031:
032: import javax.management.Attribute;
033: import javax.management.AttributeList;
034: import javax.management.AttributeNotFoundException;
035: import javax.management.DynamicMBean;
036: import javax.management.InstanceNotFoundException;
037: import javax.management.IntrospectionException;
038: import javax.management.InvalidAttributeValueException;
039: import javax.management.MBeanAttributeInfo;
040: import javax.management.MBeanException;
041: import javax.management.MBeanInfo;
042: import javax.management.MBeanOperationInfo;
043: import javax.management.MBeanServer;
044: import javax.management.ObjectName;
045: import javax.management.ReflectionException;
046: import javax.management.RuntimeErrorException;
047: import javax.management.RuntimeMBeanException;
048: import javax.management.RuntimeOperationsException;
049:
050: /**
051: * Invocation handler for MBean proxies.
052: *
053: * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
054: * @version $Revision: 57200 $
055: *
056: */
057: public class JMXInvocationHandler implements ProxyContext,
058: InvocationHandler, Serializable {
059: private static final long serialVersionUID = 3714728148040623702L;
060:
061: // Attributes -------------------------------------------------
062:
063: /**
064: * Reference to the MBean server this proxy connects to.
065: */
066: protected MBeanServer server = null;
067:
068: /**
069: * The object name of the MBean this proxy represents.
070: */
071: protected ObjectName objectName = null;
072:
073: /**
074: * Default exception handler for the proxy.
075: */
076: private ProxyExceptionHandler handler = new DefaultExceptionHandler();
077:
078: /**
079: * MBean attribute meta data.
080: */
081: private HashMap attributeMap = new HashMap();
082:
083: /**
084: * Indicates whether Object.toString() should be delegated to the resource
085: * or handled by the proxy.
086: */
087: private boolean delegateToStringToResource = false;
088:
089: /**
090: * Indicates whether Object.equals() should be delegated to the resource
091: * or handled by the proxy.
092: */
093: private boolean delegateEqualsToResource = false;
094:
095: /**
096: * Indicates whether Object.hashCode() should be delegated to the resource
097: * or handled by the proxy.
098: */
099: private boolean delegateHashCodeToResource = false;
100:
101: // Constructors -----------------------------------------------
102:
103: /**
104: * Constructs a new JMX MBean Proxy invocation handler.
105: *
106: * @param server reference to the MBean server this proxy connects to
107: * @param name object name of the MBean this proxy represents
108: *
109: * @throws MBeanProxyCreationException wraps underlying JMX exceptions in
110: * case the proxy creation fails
111: */
112: public JMXInvocationHandler(MBeanServer server, ObjectName name)
113: throws MBeanProxyCreationException {
114: try {
115: if (server == null)
116: throw new MBeanProxyCreationException(
117: "null agent reference");
118:
119: this .server = server;
120: this .objectName = name;
121:
122: MBeanInfo info = server.getMBeanInfo(objectName);
123: MBeanAttributeInfo[] attributes = info.getAttributes();
124: MBeanOperationInfo[] operations = info.getOperations();
125:
126: // collect the MBean attribute metadata for standard mbean proxies
127: for (int i = 0; i < attributes.length; ++i)
128: attributeMap
129: .put(attributes[i].getName(), attributes[i]);
130:
131: // Check whether the target resource exposes the common object methods.
132: // Dynamic Proxy will delegate these methods automatically to the
133: // invoke() implementation.
134: for (int i = 0; i < operations.length; ++i) {
135: if (operations[i].getName().equals("toString")
136: && operations[i].getReturnType().equals(
137: "java.lang.String")
138: && operations[i].getSignature().length == 0) {
139: delegateToStringToResource = true;
140: }
141:
142: else if (operations[i].getName().equals("equals")
143: && operations[i].getReturnType().equals(
144: Boolean.TYPE.getName())
145: && operations[i].getSignature().length == 1
146: && operations[i].getSignature()[0].getType()
147: .equals("java.lang.Object")) {
148: delegateEqualsToResource = true;
149: }
150:
151: else if (operations[i].getName().equals("hashCode")
152: && operations[i].getReturnType().equals(
153: Integer.TYPE.getName())
154: && operations[i].getSignature().length == 0) {
155: delegateHashCodeToResource = true;
156: }
157: }
158: } catch (InstanceNotFoundException e) {
159: throw new MBeanProxyCreationException("Object name " + name
160: + " not found: " + e.toString());
161: } catch (IntrospectionException e) {
162: throw new MBeanProxyCreationException(e.toString());
163: } catch (ReflectionException e) {
164: throw new MBeanProxyCreationException(e.toString());
165: }
166: }
167:
168: // InvocationHandler implementation ---------------------------
169:
170: public Object invoke(Object proxy, Method method, Object[] args)
171: throws Exception {
172: Class declaringClass = method.getDeclaringClass();
173:
174: // Handle methods from Object class. If the target resource exposes
175: // operation metadata with same signature then the invocations will be
176: // delegated to the target. Otherwise this instance of invocation handler
177: // will execute them.
178: if (declaringClass == Object.class)
179: return handleObjectMethods(method, args);
180:
181: // Check methods from ProxyContext interface. If invoked, delegate
182: // to the context implementation part of this invocation handler.
183: if (declaringClass == ProxyContext.class)
184: return method.invoke(this , args);
185:
186: // Check methods from DynamicMBean interface. This allows the proxy
187: // to be used in cases where the underlying metadata has changed (a la
188: // Dynamic MBean).
189: if (declaringClass == DynamicMBean.class)
190: return handleDynamicMBeanInvocation(method, args);
191:
192: try {
193: String methodName = method.getName();
194:
195: // Assume a get/setAttribute convention on the typed proxy interface.
196: // If the MBean metadata exposes a matching attribute then use the
197: // MBeanServer attribute accessors to read/modify the value. If not,
198: // fallback to MBeanServer.invoke() assuming this is an operation
199: // invocation despite the accessor naming convention.
200:
201: // getter
202: if (methodName.startsWith("get") && args == null) {
203: String attrName = methodName.substring(3, methodName
204: .length());
205:
206: // check that the metadata exists
207: MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap
208: .get(attrName);
209: if (info != null) {
210: String retType = method.getReturnType().getName();
211:
212: // check for correct return type on the getter
213: if (retType.equals(info.getType())) {
214: return server
215: .getAttribute(objectName, attrName);
216: }
217: }
218: }
219:
220: // boolean getter
221: else if (methodName.startsWith("is") && args == null) {
222: String attrName = methodName.substring(2, methodName
223: .length());
224:
225: // check that the metadata exists
226: MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap
227: .get(attrName);
228: if (info != null && info.isIs()) {
229: Class retType = method.getReturnType();
230:
231: // check for correct return type on the getter
232: if (retType.equals(Boolean.class)
233: || retType.equals(Boolean.TYPE)) {
234: return server
235: .getAttribute(objectName, attrName);
236: }
237: }
238: }
239:
240: // setter
241: else if (methodName.startsWith("set") && args != null
242: && args.length == 1) {
243: String attrName = methodName.substring(3, methodName
244: .length());
245:
246: // check that the metadata exists
247: MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap
248: .get(attrName);
249: if (info != null
250: && method.getReturnType().equals(Void.TYPE)) {
251: ClassLoader cl = Thread.currentThread()
252: .getContextClassLoader();
253:
254: Class signatureClass = null;
255: String classType = info.getType();
256:
257: if (isPrimitive(classType))
258: signatureClass = getPrimitiveClass(classType);
259: else
260: signatureClass = cl.loadClass(info.getType());
261:
262: if (signatureClass.isAssignableFrom(args[0]
263: .getClass())) {
264: server.setAttribute(objectName, new Attribute(
265: attrName, args[0]));
266: return null;
267: }
268: }
269: }
270:
271: String[] signature = null;
272:
273: if (args != null) {
274: signature = new String[args.length];
275: Class[] sign = method.getParameterTypes();
276:
277: for (int i = 0; i < sign.length; ++i)
278: signature[i] = sign[i].getName();
279: }
280:
281: return server.invoke(objectName, methodName, args,
282: signature);
283: } catch (InstanceNotFoundException e) {
284: return getExceptionHandler().handleInstanceNotFound(this ,
285: e, method, args);
286: } catch (AttributeNotFoundException e) {
287: return getExceptionHandler().handleAttributeNotFound(this ,
288: e, method, args);
289: } catch (InvalidAttributeValueException e) {
290: return getExceptionHandler().handleInvalidAttributeValue(
291: this , e, method, args);
292: } catch (MBeanException e) {
293: return getExceptionHandler().handleMBeanException(this , e,
294: method, args);
295: } catch (ReflectionException e) {
296: return getExceptionHandler().handleReflectionException(
297: this , e, method, args);
298: } catch (RuntimeOperationsException e) {
299: return getExceptionHandler()
300: .handleRuntimeOperationsException(this , e, method,
301: args);
302: } catch (RuntimeMBeanException e) {
303: return getExceptionHandler().handleRuntimeMBeanException(
304: this , e, method, args);
305: } catch (RuntimeErrorException e) {
306: return getExceptionHandler().handleRuntimeError(this , e,
307: method, args);
308: }
309: }
310:
311: public ProxyExceptionHandler getExceptionHandler() {
312: return handler;
313: }
314:
315: // ProxyContext implementation -----------------------------------
316:
317: // The proxy provides an access point for the client to methods not part
318: // of the MBean's management interface. It can be used to configure the
319: // invocation (with context, client side interceptors, RPC), exception
320: // handling, act as an access point to MBean server interface and so on.
321:
322: public void setExceptionHandler(ProxyExceptionHandler handler) {
323: this .handler = handler;
324: }
325:
326: public MBeanServer getMBeanServer() {
327: return server;
328: }
329:
330: public ObjectName getObjectName() {
331: return objectName;
332: }
333:
334: // Object overrides ----------------------------------------------
335:
336: public String toString() {
337: return "MBeanProxy for " + objectName + " (Agent ID: "
338: + AgentID.get(server) + ")";
339: }
340:
341: // Private -------------------------------------------------------
342:
343: private Object handleObjectMethods(Method method, Object[] args)
344: throws InstanceNotFoundException, ReflectionException,
345: IntrospectionException, MBeanException {
346: if (method.getName().equals("toString")) {
347: if (delegateToStringToResource)
348: return server
349: .invoke(objectName, "toString", null, null);
350: else
351: return toString();
352: }
353:
354: else if (method.getName().equals("equals")) {
355: if (delegateEqualsToResource) {
356: return server.invoke(objectName, "equals",
357: new Object[] { args[0] },
358: new String[] { "java.lang.Object" });
359: } else if (Proxy.isProxyClass(args[0].getClass())) {
360: Proxy prxy = (Proxy) args[0];
361: return new Boolean(this .equals(Proxy
362: .getInvocationHandler(prxy)));
363: } else {
364: return new Boolean(this .equals(args[0]));
365: }
366: }
367:
368: else if (method.getName().equals("hashCode")) {
369: if (delegateHashCodeToResource)
370: return server
371: .invoke(objectName, "hashCode", null, null);
372: else
373: return new Integer(this .hashCode());
374: }
375:
376: else
377: throw new Error("Unexpected method invocation!");
378: }
379:
380: private Object handleDynamicMBeanInvocation(Method method,
381: Object[] args) throws InstanceNotFoundException,
382: ReflectionException, IntrospectionException,
383: MBeanException, AttributeNotFoundException,
384: InvalidAttributeValueException {
385: String methodName = method.getName();
386:
387: if (methodName.equals("setAttribute")) {
388: server.setAttribute(objectName, (Attribute) args[0]);
389: return null;
390: } else if (methodName.equals("setAttributes"))
391: return server.setAttributes(objectName,
392: (AttributeList) args[0]);
393: else if (methodName.equals("getAttribute"))
394: return server.getAttribute(objectName, (String) args[0]);
395: else if (methodName.equals("getAttributes"))
396: return server.getAttributes(objectName, (String[]) args[0]);
397: else if (methodName.equals("invoke"))
398: return server.invoke(objectName, (String) args[0],
399: (Object[]) args[1], (String[]) args[2]);
400: else if (methodName.equals("getMBeanInfo"))
401: return server.getMBeanInfo(objectName);
402:
403: else
404: throw new Error("Unexpected method invocation!");
405: }
406:
407: private boolean isPrimitive(String type) {
408: if (type.equals(Integer.TYPE.getName()))
409: return true;
410: if (type.equals(Long.TYPE.getName()))
411: return true;
412: if (type.equals(Boolean.TYPE.getName()))
413: return true;
414: if (type.equals(Byte.TYPE.getName()))
415: return true;
416: if (type.equals(Character.TYPE.getName()))
417: return true;
418: if (type.equals(Short.TYPE.getName()))
419: return true;
420: if (type.equals(Float.TYPE.getName()))
421: return true;
422: if (type.equals(Double.TYPE.getName()))
423: return true;
424: if (type.equals(Void.TYPE.getName()))
425: return true;
426:
427: return false;
428: }
429:
430: private Class getPrimitiveClass(String type) {
431: if (type.equals(Integer.TYPE.getName()))
432: return Integer.TYPE;
433: if (type.equals(Long.TYPE.getName()))
434: return Long.TYPE;
435: if (type.equals(Boolean.TYPE.getName()))
436: return Boolean.TYPE;
437: if (type.equals(Byte.TYPE.getName()))
438: return Byte.TYPE;
439: if (type.equals(Character.TYPE.getName()))
440: return Character.TYPE;
441: if (type.equals(Short.TYPE.getName()))
442: return Short.TYPE;
443: if (type.equals(Float.TYPE.getName()))
444: return Float.TYPE;
445: if (type.equals(Double.TYPE.getName()))
446: return Double.TYPE;
447: if (type.equals(Void.TYPE.getName()))
448: return Void.TYPE;
449:
450: return null;
451: }
452:
453: }
|