1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.mx.util;
23
24 import java.io.Externalizable;
25 import java.io.IOException;
26 import java.io.ObjectInput;
27 import java.io.ObjectOutput;
28 import java.lang.reflect.InvocationHandler;
29 import java.lang.reflect.Method;
30 import java.lang.reflect.Proxy;
31 import java.security.AccessController;
32 import java.security.PrivilegedAction;
33 import java.util.HashMap;
34
35 import javax.management.Attribute;
36 import javax.management.MBeanAttributeInfo;
37 import javax.management.MBeanInfo;
38 import javax.management.MBeanServer;
39 import javax.management.MBeanServerConnection;
40 import javax.management.MalformedObjectNameException;
41 import javax.management.ObjectName;
42
43 /**
44 * A factory for producing MBean proxies.
45 *
46 * <p>Created proxies will also implement {@link org.jboss.mx.util.MBeanProxyInstance}
47 * allowing access to the proxies configuration.
48 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>.
49 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>.
50 * @author <a href="mailto:adrian.brock@happeningtimes.com">Adrian Brock</a>.
51 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>.
52 * @version <tt>$Revision: 42126 $</tt>
53 */
54 public class MBeanProxyExt
55 implements InvocationHandler, MBeanProxyInstance, Externalizable
56 {
57 /** The serialVersionUID */
58 private static final long serialVersionUID = -2942844863242742655L;
59
60 /**
61 * The remote MBeanServerConnection
62 */
63 public static MBeanServerConnection remote;
64
65 /**
66 * The server to proxy invoke calls to.
67 */
68 private MBeanServerConnection server;
69
70 /**
71 * The name of the object to invoke.
72 */
73 private ObjectName name;
74
75 /**
76 * The MBean's attributes
77 */
78 private transient final HashMap attributeMap = new HashMap();
79 /**
80 * Have the attributes been retrieved
81 */
82 private transient boolean inited = false;
83
84 /**
85 * For externalizable
86 */
87 public MBeanProxyExt()
88 {
89 }
90
91 /**
92 * Construct an MBeanProxy.
93 */
94 MBeanProxyExt(final ObjectName name, final MBeanServer server, boolean lazyInit)
95 {
96 this.name = name;
97 this.server = server;
98 if (lazyInit == false)
99 init();
100 }
101
102 /**
103 * Used when args is null.
104 */
105 private static final Object EMPTY_ARGS[] = {};
106
107 /**
108 * Invoke the configured MBean via the target MBeanServer and decode any
109 * resulting JMX exceptions that are thrown.
110 */
111 public Object invoke(final Object proxy,
112 final Method method,
113 final Object[] args)
114 throws Throwable
115 {
116 // if the method belongs to ProxyInstance, then invoke locally
117 Class type = method.getDeclaringClass();
118 if (type == MBeanProxyInstance.class || type == Object.class)
119 {
120 return method.invoke(this, args);
121 }
122
123 String methodName = method.getName();
124
125 // Get attribute
126 if (methodName.startsWith("get") && args == null)
127 {
128 if (inited == false)
129 init();
130
131 String attrName = methodName.substring(3);
132 MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap.get(attrName);
133 if (info != null)
134 {
135 String retType = method.getReturnType().getName();
136 if (retType.equals(info.getType()))
137 {
138 try
139 {
140 return server.getAttribute(name, attrName);
141 }
142 catch (Exception e)
143 {
144 throw JMXExceptionDecoder.decode(e);
145 }
146 }
147 }
148 }
149
150 // Is attribute
151 else if (methodName.startsWith("is") && args == null)
152 {
153 if (inited == false)
154 init();
155
156 String attrName = methodName.substring(2);
157 MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap.get(attrName);
158 if (info != null && info.isIs())
159 {
160 Class retType = method.getReturnType();
161 if (retType.equals(Boolean.class) || retType.equals(Boolean.TYPE))
162 {
163 try
164 {
165 return server.getAttribute(name, attrName);
166 }
167 catch (Exception e)
168 {
169 throw JMXExceptionDecoder.decode(e);
170 }
171 }
172 }
173 }
174
175 // Set attribute
176 else if (methodName.startsWith("set") && args != null && args.length == 1)
177 {
178 if (inited == false)
179 init();
180
181 String attrName = methodName.substring(3);
182 MBeanAttributeInfo info = (MBeanAttributeInfo) attributeMap.get(attrName);
183 if (info != null && method.getReturnType() == Void.TYPE)
184 {
185 try
186 {
187 server.setAttribute(name, new Attribute(attrName, args[0]));
188 return null;
189 }
190 catch (Exception e)
191 {
192 throw JMXExceptionDecoder.decode(e);
193 }
194 }
195 }
196
197 // Operation
198
199 // convert the parameter types to strings for JMX
200 Class[] types = method.getParameterTypes();
201 String[] sig = new String[types.length];
202 for (int i = 0; i < types.length; i++)
203 {
204 sig[i] = types[i].getName();
205 }
206
207 // invoke the server and decode JMX exceptions
208 try
209 {
210 return server.invoke(name, methodName, args == null ? EMPTY_ARGS : args, sig);
211 }
212 catch (Exception e)
213 {
214 throw JMXExceptionDecoder.decode(e);
215 }
216 }
217
218
219 ///////////////////////////////////////////////////////////////////////////
220 // MBeanProxyInstance //
221 ///////////////////////////////////////////////////////////////////////////
222
223 public final ObjectName getMBeanProxyObjectName()
224 {
225 return name;
226 }
227
228 public final MBeanServer getMBeanProxyMBeanServer()
229 {
230 if (server instanceof MBeanServer == false)
231 throw new IllegalStateException("This operation is not available for an MBeanServerConnection");
232 return (MBeanServer) server;
233 }
234
235 public final MBeanServerConnection getMBeanProxyMBeanServerConnection()
236 {
237 return server;
238 }
239
240 ///////////////////////////////////////////////////////////////////////////
241 // Object Overrides //
242 ///////////////////////////////////////////////////////////////////////////
243
244 /**
245 * We need to override this because by default equals returns false when
246 * called on the proxy object and then relayed here.
247 */
248 public boolean equals(Object that)
249 {
250 if (that == null) return false;
251 if (that == this) return true;
252
253 // check if 'that' is an MBeanProxyExt or a Proxy instance
254 // that implements the MBeanProxyInstance interface
255 if (that instanceof MBeanProxyInstance)
256 {
257 MBeanProxyInstance proxy = (MBeanProxyInstance) that;
258
259 // assume equality if both the MBeanServer and ObjectName match
260 if (name.equals(proxy.getMBeanProxyObjectName()) &&
261 server.equals(proxy.getMBeanProxyMBeanServer()))
262 {
263 return true;
264 }
265 }
266 return false;
267 }
268
269 /**
270 * As with equals, use the MBeanServer + ObjectName to calculate the
271 * hashCode
272 */
273 public int hashCode()
274 {
275 return name.hashCode() * 31 + server.hashCode();
276 }
277
278 /**
279 * avoid the default printout, e.g. org.jboss.mx.util.MBeanProxyExt@120540c
280 */
281 public String toString()
282 {
283 StringBuffer sbuf = new StringBuffer(128);
284
285 sbuf.append("MBeanProxyExt[").append(name.toString()).append(']');
286
287 return sbuf.toString();
288 }
289
290 ///////////////////////////////////////////////////////////////////////////
291 // Factory Methods //
292 ///////////////////////////////////////////////////////////////////////////
293
294 /**
295 * Create an MBean proxy.
296 * @param intf The interface which the proxy will implement.
297 * @param name A string used to construct the ObjectName of the MBean to
298 * proxy to.
299 * @return A MBean proxy.
300 * @throws javax.management.MalformedObjectNameException Invalid object
301 * name.
302 */
303 public static Object create(final Class intf, final String name)
304 throws MalformedObjectNameException
305 {
306 return create(intf, new ObjectName(name));
307 }
308
309 /**
310 * Create an MBean proxy.
311 * @param intf The interface which the proxy will implement.
312 * @param name A string used to construct the ObjectName of the MBean to
313 * proxy to.
314 * @param server The MBeanServer that contains the MBean to proxy to.
315 * @return A MBean proxy.
316 * @throws javax.management.MalformedObjectNameException Invalid object
317 * name.
318 */
319 public static Object create(final Class intf,
320 final String name,
321 final MBeanServer server)
322 throws MalformedObjectNameException
323 {
324 return create(intf, new ObjectName(name), server);
325 }
326
327 /**
328 * Create an MBean proxy.
329 * @param intf The interface which the proxy will implement.
330 * @param name The name of the MBean to proxy invocations to.
331 * @return A MBean proxy.
332 */
333 public static Object create(final Class intf, final ObjectName name)
334 {
335 return create(intf, name, MBeanServerLocator.locateJBoss());
336 }
337
338 /**
339 * Create an MBean proxy.
340 * @param intf The interface which the proxy will implement.
341 * @param name The name of the MBean to proxy invocations to.
342 * @param server The MBeanServer that contains the MBean to proxy to.
343 * @return A MBean proxy.
344 */
345 public static Object create(final Class intf,
346 final ObjectName name,
347 final MBeanServer server)
348 {
349 return create(intf, name, server, false);
350 }
351
352 /**
353 * Create an MBean proxy.
354 * @param intf The interface which the proxy will implement.
355 * @param name The name of the MBean to proxy invocations to.
356 * @param server The MBeanServer that contains the MBean to proxy to.
357 * @param lazyInit - a flag indicating if the mbean attribute info should
358 * be retrieved when the proxy is created.
359 * @return A MBean proxy.
360 */
361 public static Object create(final Class intf, final ObjectName name,
362 final MBeanServer server, boolean lazyInit)
363 {
364 // CL which delegates to MBeanProxyInstance's cl for it's class resolution
365 PrivilegedAction action = new PrivilegedAction()
366 {
367 public Object run()
368 {
369 ClassLoader cl = new ClassLoader(intf.getClassLoader())
370 {
371 public Class loadClass(final String className) throws ClassNotFoundException
372 {
373 try
374 {
375 return super.loadClass(className);
376 }
377 catch (ClassNotFoundException e)
378 {
379 // only allow loading of MBeanProxyInstance from this loader
380 if (className.equals(MBeanProxyInstance.class.getName()))
381 {
382 return MBeanProxyInstance.class.getClassLoader().loadClass(className);
383 }
384 // was some other classname, throw the CNFE
385 throw e;
386 }
387 }
388 };
389 return cl;
390 }
391 };
392 ClassLoader cl = (ClassLoader) AccessController.doPrivileged(action);
393 Class[] ifaces = {MBeanProxyInstance.class, intf};
394 InvocationHandler handler = new MBeanProxyExt(name, server, lazyInit);
395 return Proxy.newProxyInstance(cl, ifaces, handler);
396 }
397
398 /**
399 * Retrieve the mbean MBeanAttributeInfo
400 */
401 private synchronized void init()
402 {
403 // The MBean's attributes
404 inited = true;
405 try
406 {
407 MBeanInfo info = server.getMBeanInfo(name);
408 MBeanAttributeInfo[] attributes = info.getAttributes();
409
410 for (int i = 0; i < attributes.length; ++i)
411 attributeMap.put(attributes[i].getName(), attributes[i]);
412 }
413 catch (Exception e)
414 {
415 throw new RuntimeException("Error creating MBeanProxy: " + name, e);
416 }
417 }
418
419 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
420 {
421 name = (ObjectName) in.readObject();
422 server = (MBeanServerConnection) in.readObject();
423 }
424
425
426 public void writeExternal(ObjectOutput out) throws IOException
427 {
428 out.writeObject(name);
429 if (remote != null)
430 out.writeObject(remote);
431 else
432 out.writeObject(server); // This will fail for a normal MBeanServer
433 }
434 }