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 javax.management;
023:
024: import java.lang.reflect.InvocationHandler;
025: import java.lang.reflect.Method;
026: import java.lang.reflect.Proxy;
027: import java.util.HashMap;
028:
029: /**
030: * An invocation handler to proxy requests through the MBeanServer
031: *
032: * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>.
033: * @version $Revision: 57200 $
034: */
035: public class MBeanServerInvocationHandler implements InvocationHandler {
036: // Constants ---------------------------------------------------
037:
038: // Attributes --------------------------------------------------
039:
040: /**
041: * The MBeanServerConnection
042: */
043: private MBeanServerConnection connection;
044:
045: /**
046: * The ObjectName
047: */
048: private ObjectName name;
049:
050: /**
051: * The method mappings
052: */
053: private HashMap mappings;
054:
055: // Static -----------------------------------------------------
056:
057: // Signatures
058: private static final String[] EMPTY_SIGNATURE = new String[0];
059: private static final String[] EQUALS_SIGNATURE = new String[] { Object.class
060: .getName() };
061: private static final Class[] LISTENER = new Class[] { NotificationListener.class };
062: private static final Class[] TRIPLET = new Class[] {
063: NotificationListener.class, NotificationFilter.class,
064: Object.class };
065:
066: // Object methods
067: private static final Method EQUALS;
068: private static final Method HASH_CODE;
069: private static final Method TO_STRING;
070:
071: // NotificationEmitter methods
072: private static final Method ADD_NOTIFICATION_LISTENER;
073: private static final Method GET_NOTIFICATION_INFO;
074: private static final Method REMOVE_NOTIFICATION_LISTENER;
075: private static final Method REMOVE_NOTIFICATION_LISTENER_TRIPLET;
076:
077: static {
078: try {
079: ADD_NOTIFICATION_LISTENER = NotificationBroadcaster.class
080: .getDeclaredMethod("addNotificationListener",
081: TRIPLET);
082: GET_NOTIFICATION_INFO = NotificationBroadcaster.class
083: .getDeclaredMethod("getNotificationInfo",
084: new Class[0]);
085: REMOVE_NOTIFICATION_LISTENER = NotificationBroadcaster.class
086: .getDeclaredMethod("removeNotificationListener",
087: LISTENER);
088: REMOVE_NOTIFICATION_LISTENER_TRIPLET = NotificationEmitter.class
089: .getDeclaredMethod("removeNotificationListener",
090: TRIPLET);
091: EQUALS = Object.class.getDeclaredMethod("equals",
092: new Class[] { Object.class });
093: HASH_CODE = Object.class.getDeclaredMethod("hashCode",
094: new Class[0]);
095: TO_STRING = Object.class.getDeclaredMethod("toString",
096: new Class[0]);
097: } catch (Exception e) {
098: throw new RuntimeException(e.toString());
099: }
100: }
101:
102: // Constructors ------------------------------------------------
103:
104: /**
105: * Construct an MBeanServerInvocationHandler for the
106: * given connection and object name.<p>
107: *
108: * @param connection the MBeanServerConnection
109: * @param name the ObjectName of the bean
110: */
111: public MBeanServerInvocationHandler(
112: MBeanServerConnection connection, ObjectName name) {
113: // WARNING: These checks are not in the spec
114: if (connection == null)
115: throw new IllegalArgumentException("Null connection");
116: if (name == null)
117: throw new IllegalArgumentException("Null name");
118: if (name.isPattern())
119: throw new IllegalArgumentException("Name is a pattern");
120:
121: this .connection = connection;
122: this .name = name;
123: }
124:
125: /**
126: * Constructs a new Proxy for the MBean using an
127: * MBeanServerInvocationHandler.
128: *
129: * @param connection the MBeanServerConnection
130: * @param name the ObjectName of the bean
131: * @param interfaceClass the interface to expose
132: * @param broadcaster pass true to expose the NotificationEmitter interface
133: */
134: public static Object newProxyInstance(
135: MBeanServerConnection connection, ObjectName name,
136: Class interfaceClass, boolean broadcaster) {
137: MBeanServerInvocationHandler handler = new MBeanServerInvocationHandler(
138: connection, name);
139:
140: // WARNING: These checks are not in the spec
141: if (interfaceClass == null)
142: throw new IllegalArgumentException("Null interface");
143:
144: Class[] interfaces;
145: if (broadcaster)
146: interfaces = new Class[] { interfaceClass,
147: NotificationEmitter.class };
148: else
149: interfaces = new Class[] { interfaceClass };
150:
151: return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
152: interfaces, handler);
153: }
154:
155: // Public ------------------------------------------------------
156:
157: // InvocationHandler Implementation ----------------------------
158:
159: public Object invoke(Object proxy, Method method, Object[] args)
160: throws Throwable {
161: return getAction(proxy, method).perform(args);
162: }
163:
164: // Y Overrides -------------------------------------------------
165:
166: // Protected ---------------------------------------------------
167:
168: // Package Private ---------------------------------------------
169:
170: // Private -----------------------------------------------------
171:
172: /**
173: * Get the actions for this method
174: */
175: private Action getAction(Object proxy, Method method)
176: throws Throwable {
177: // Doesn't really matter if the mappings are
178: // setup twice by two different threads, they are the same.
179: if (mappings == null)
180: mappings = getMappings(proxy);
181:
182: // Check the action
183: Action result = (Action) mappings.get(method);
184: if (result == null)
185: throw new IllegalArgumentException("Unknown method "
186: + method);
187:
188: // Return the result
189: return result;
190: }
191:
192: /**
193: * Set up the mappings
194: */
195: private HashMap getMappings(Object proxy) throws Throwable {
196: HashMap result = new HashMap();
197:
198: Class[] interfaces = proxy.getClass().getInterfaces();
199: for (int i = 0; i < interfaces.length; ++i) {
200: if (interfaces[i].equals(NotificationEmitter.class)) {
201: result.put(ADD_NOTIFICATION_LISTENER,
202: new AddNotificationListenerAction());
203: result.put(GET_NOTIFICATION_INFO,
204: new GetNotificationInfoAction());
205: result.put(REMOVE_NOTIFICATION_LISTENER,
206: new RemoveNotificationListenerAction());
207: result.put(REMOVE_NOTIFICATION_LISTENER_TRIPLET,
208: new RemoveNotificationListenerTripletAction());
209: } else {
210: Method[] methods = interfaces[i].getMethods();
211:
212: for (int m = 0; m < methods.length; ++m) {
213: String methodName = methods[m].getName();
214: Class returnType = methods[m].getReturnType();
215: Class[] parameterTypes = methods[m]
216: .getParameterTypes();
217:
218: // Getter
219: if (methodName.startsWith("get")
220: && methodName.length() > 3
221: && Void.TYPE.equals(returnType) == false
222: && parameterTypes.length == 0) {
223: result.put(methods[m], new GetAction(methodName
224: .substring(3)));
225: }
226: // Getter (is)
227: else if (methodName.startsWith("is")
228: && methodName.length() > 2
229: && (Boolean.TYPE.equals(returnType) || Boolean.class
230: .equals(returnType))
231: && parameterTypes.length == 0) {
232: result.put(methods[m], new GetAction(methodName
233: .substring(2)));
234: }
235: // Setter (is)
236: else if (methodName.startsWith("set")
237: && methodName.length() > 3
238: && Void.TYPE.equals(returnType)
239: && parameterTypes.length == 1) {
240: result.put(methods[m], new SetAction(methodName
241: .substring(3)));
242: }
243: // Invoker
244: else {
245: result.put(methods[m], new InvokeAction(
246: methodName,
247: getSignature(parameterTypes)));
248: }
249: }
250: }
251: }
252:
253: // Add the Object mappings
254: result.put(EQUALS, new InvokeAction(EQUALS.getName(),
255: EQUALS_SIGNATURE));
256: result.put(HASH_CODE, new InvokeAction(HASH_CODE.getName(),
257: EMPTY_SIGNATURE));
258: result.put(TO_STRING, new InvokeAction(TO_STRING.getName(),
259: EMPTY_SIGNATURE));
260:
261: return result;
262: }
263:
264: private static String[] getSignature(final Class[] parameterTypes) {
265: String[] signature = new String[parameterTypes.length];
266: for (int p = 0; p < parameterTypes.length; ++p)
267: signature[p] = parameterTypes[p].getName();
268: return signature;
269: }
270:
271: // Inner Classes -----------------------------------------------
272:
273: private interface Action {
274: public Object perform(Object[] args) throws Throwable;
275: }
276:
277: private class GetAction implements Action {
278: private String attribute;
279:
280: public GetAction(String attribute) {
281: this .attribute = attribute;
282: }
283:
284: public Object perform(Object[] args) throws Throwable {
285: try {
286: return connection.getAttribute(name, attribute);
287: } catch (MBeanException exception) {
288: throw exception.getTargetException();
289: }
290: }
291: }
292:
293: private class SetAction implements Action {
294: private String attribute;
295:
296: public SetAction(String attribute) {
297: this .attribute = attribute;
298: }
299:
300: public Object perform(Object[] args) throws Throwable {
301: try {
302: connection.setAttribute(name, new Attribute(attribute,
303: args[0]));
304: return null;
305: } catch (MBeanException exception) {
306: throw exception.getTargetException();
307: }
308: }
309: }
310:
311: private class InvokeAction implements Action {
312: private String operation;
313: private String[] signature;
314:
315: public InvokeAction(String operation, String[] signature) {
316: this .operation = operation;
317: this .signature = signature;
318: }
319:
320: public Object perform(Object[] args) throws Throwable {
321: try {
322: return connection.invoke(name, operation, args,
323: signature);
324: } catch (MBeanException exception) {
325: throw exception.getTargetException();
326: }
327: }
328: }
329:
330: private class AddNotificationListenerAction implements Action {
331: public Object perform(Object[] args) throws Throwable {
332: connection.addNotificationListener(name,
333: (NotificationListener) args[0],
334: (NotificationFilter) args[1], args[2]);
335: return null;
336: }
337: }
338:
339: private class GetNotificationInfoAction implements Action {
340: public Object perform(Object[] args) throws Throwable {
341: return connection.getMBeanInfo(name).getNotifications();
342: }
343: }
344:
345: private class RemoveNotificationListenerAction implements Action {
346: public Object perform(Object[] args) throws Throwable {
347: connection.removeNotificationListener(name,
348: (NotificationListener) args[0]);
349: return null;
350: }
351: }
352:
353: private class RemoveNotificationListenerTripletAction implements
354: Action {
355: public Object perform(Object[] args) throws Throwable {
356: connection.removeNotificationListener(name,
357: (NotificationListener) args[0],
358: (NotificationFilter) args[1], args[2]);
359: return null;
360: }
361: }
362: }
|