001 /* MBeanServerInvocationHandler.java -- Provides a proxy for a bean. 002 Copyright (C) 2007 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 package javax.management; 039 040 import gnu.javax.management.Translator; 041 042 import java.lang.reflect.InvocationHandler; 043 import java.lang.reflect.Method; 044 import java.lang.reflect.Proxy; 045 046 /** 047 * <p> 048 * Provides a proxy for a management bean. The methods 049 * of the given interface are fulfilled by redirecting the 050 * calls over an {@link MBeanServerConnection} to the bean 051 * specified by the supplied {@link ObjectName}. 052 * </p> 053 * <p> 054 * The {@link java.lang.reflect.InvocationHandler} also makes 055 * provision for {@link MXBean}s by providing type conversion 056 * according to the rules defined for these beans. The input 057 * parameters are converted to their equivalent open type before 058 * calling the method, and then the return value is converted 059 * back from its open type to the appropriate Java type. For 060 * example, a method that takes an {@link Enum} as input and 061 * returns a {@link java.util.List} will have the input value 062 * converted from an {@link Enum} to a {@link String}, while 063 * the return value will be converted from its return type 064 * (an appropriately typed array) to a {@link java.util.List}. 065 * </p> 066 * <p> 067 * The proxy has special cases for the {@link Object#equals(Object)}, 068 * {@link Object#hashCode()} and {@link Object#toString()} methods. 069 * Unless they are specified explictly by the interface, the 070 * following behaviour is provided for these methods by the proxy: 071 * </p> 072 * <ul> 073 * <li><code>equals(Object)</code> returns true if the other object 074 * is an {@link MBeanServerInvocationHandler} with the same 075 * {@link MBeanServerConnection} and {@link ObjectName}. If an 076 * interface class was specified on construction for one of the 077 * proxies, then the same class must have also been specified 078 * for the other.</li> 079 * <li><code>hashCode()</code> returns the same value for 080 * equivalent proxies.</li> 081 * <li><code>toString()</code> returns a textual representation 082 * of the proxy.</li> 083 * </ul> 084 * 085 * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 086 * @since 1.5 087 */ 088 public class MBeanServerInvocationHandler 089 implements InvocationHandler 090 { 091 092 /** 093 * The connection used to make the calls. 094 */ 095 private MBeanServerConnection conn; 096 097 /** 098 * The name of the bean to perform operations on. 099 */ 100 private ObjectName name; 101 102 /** 103 * True if this proxy is for an {@link MXBean}. 104 */ 105 private boolean mxBean; 106 107 /** 108 * The interface class associated with the bean. 109 */ 110 private Class<?> iface; 111 112 /** 113 * Constructs a new {@link MBeanServerInvocationHandler} 114 * which forwards methods to the supplied bean via the 115 * given {@link MBeanServerConnection}. This constructor 116 * is used in preference to 117 * {@link JMX#newMBeanProxy(MBeanServerConnection, ObjectName, 118 * Class<T>)} if you wish to make your own call to 119 * {@link java.lang.reflect.Proxy#newInstance(ClassLoader, 120 * Class[], java.lang.reflect.InvocationHandler)} with 121 * a different {@link ClassLoader}. Calling this constructor 122 * is equivalent to <code>MBeanServerInvocationHandler(conn, 123 * name, false)</code>. The other constructor should be used 124 * instead if the bean being proxied is an {@link MXBean}. 125 * 126 * @param conn the connection through which methods will 127 * be forwarded to the bean. 128 * @param name the name of the bean to use to provide the 129 * actual calls. 130 */ 131 public MBeanServerInvocationHandler(MBeanServerConnection conn, 132 ObjectName name) 133 { 134 this(conn, name, false); 135 } 136 137 /** 138 * Constructs a new {@link MBeanServerInvocationHandler} 139 * which forwards methods to the supplied bean via the 140 * given {@link MBeanServerConnection}. This constructor 141 * is used in preference to 142 * {@link JMX#newMBeanProxy(MBeanServerConnection, ObjectName, 143 * Class<T>)} if you wish to make your own call to 144 * {@link java.lang.reflect.Proxy#newInstance(ClassLoader, 145 * Class[], java.lang.reflect.InvocationHandler)} with 146 * a different {@link ClassLoader}. 147 * 148 * @param conn the connection through which methods will 149 * be forwarded to the bean. 150 * @param name the name of the bean to use to provide the 151 * actual calls. 152 * @param mxBean true if the bean being proxied is an 153 * {@link MXBean}. 154 * @since 1.6 155 */ 156 public MBeanServerInvocationHandler(MBeanServerConnection conn, 157 ObjectName name, boolean mxBean) 158 { 159 this.conn = conn; 160 this.name = name; 161 this.mxBean = mxBean; 162 } 163 164 /** 165 * Returns the connection through which the calls to the bean 166 * will be made. 167 * 168 * @return the connection being used to forward the calls to 169 * the bean. 170 * @since 1.6 171 */ 172 public MBeanServerConnection getMBeanServerConnection() 173 { 174 return conn; 175 } 176 177 /** 178 * Returns the name of the bean to which method calls are made. 179 * 180 * @return the bean which provides the actual method calls. 181 * @since 1.6 182 */ 183 public ObjectName getObjectName() 184 { 185 return name; 186 } 187 188 /** 189 * Called by the proxy class whenever a method is called. The method 190 * is emulated by retrieving an attribute from, setting an attribute on 191 * or invoking a method on the server connection as required. Translation 192 * between the Java data types supplied as arguments to the open types used 193 * by the bean is provided, as well as translation of the return value back 194 * in to the appropriate Java type if the bean is an {@link MXBean}. 195 * 196 * @param proxy the proxy on which the method was called. 197 * @param method the method which was called. 198 * @param args the arguments supplied to the method. 199 * @return the return value from the method. 200 * @throws Throwable if an exception is thrown in performing the 201 * method emulation. 202 */ 203 public Object invoke(Object proxy, Method method, Object[] args) 204 throws Throwable 205 { 206 String mName = method.getName(); 207 Class proxyClass = proxy.getClass(); 208 if (mName.equals("toString")) 209 { 210 if (inInterface(mName, proxyClass)) 211 return conn.invoke(name,mName,null,null); 212 else 213 return proxyClass.getName() + "[name=" + name 214 + ", conn=" + conn + "]"; 215 } 216 if (mName.equals("hashCode")) 217 { 218 if (inInterface(mName, proxyClass)) 219 return conn.invoke(name,mName,null,null); 220 else 221 return conn.hashCode() + name.hashCode() 222 + (iface == null ? 0 : iface.hashCode()); 223 } 224 if (mName.equals("equals")) 225 { 226 if (inInterface(mName, proxyClass, Object.class)) 227 return conn.invoke(name,mName,new Object[]{args[0]}, 228 new String[]{"java.lang.Object"}); 229 else 230 { 231 if (args[0].getClass() != proxy.getClass()) 232 return false; 233 InvocationHandler ih = Proxy.getInvocationHandler(args[0]); 234 if (ih instanceof MBeanServerInvocationHandler) 235 { 236 MBeanServerInvocationHandler h = 237 (MBeanServerInvocationHandler) ih; 238 return conn.equals(h.getMBeanServerConnection()) 239 && name.equals(h.getObjectName()) 240 && (iface == null ? h.iface == null 241 : iface.equals(h.iface)); 242 } 243 return false; 244 } 245 } 246 if (NotificationEmitter.class.isAssignableFrom(proxyClass)) 247 { 248 if (mName.equals("addNotificationListener")) 249 { 250 conn.addNotificationListener(name, 251 (NotificationListener) args[0], 252 (NotificationFilter) args[1], 253 args[2]); 254 return null; 255 } 256 if (mName.equals("getNotificationInfo")) 257 return conn.getMBeanInfo(name).getNotifications(); 258 if (mName.equals("removeNotificationListener")) 259 { 260 if (args.length == 1) 261 conn.removeNotificationListener(name, 262 (NotificationListener) 263 args[0]); 264 else 265 conn.removeNotificationListener(name, 266 (NotificationListener) 267 args[0], 268 (NotificationFilter) 269 args[1], args[2]); 270 return null; 271 } 272 } 273 String[] sigs; 274 if (args == null) 275 sigs = null; 276 else 277 { 278 sigs = new String[args.length]; 279 for (int a = 0; a < args.length; ++a) 280 sigs[a] = args[a].getClass().getName(); 281 } 282 String attrib = null; 283 if (mName.startsWith("get")) 284 attrib = mName.substring(3); 285 else if (mName.startsWith("is")) 286 attrib = mName.substring(2); 287 if (attrib != null) 288 { 289 Object val = conn.getAttribute(name, attrib); 290 if (mxBean) 291 return Translator.toJava(val, method); 292 else 293 return val; 294 } 295 else if (mName.startsWith("set")) 296 { 297 Object arg; 298 if (mxBean) 299 arg = Translator.fromJava(args, method)[0]; 300 else 301 arg = args[0]; 302 conn.setAttribute(name, new Attribute(mName.substring(3), arg)); 303 return null; 304 } 305 if (mxBean) 306 return Translator.toJava(conn.invoke(name, mName, 307 Translator.fromJava(args,method), 308 sigs), method); 309 else 310 return conn.invoke(name, mName, args, sigs); 311 } 312 313 /** 314 * Returns true if this is a proxy for an {@link MXBean} 315 * and conversions must be applied to input parameters 316 * and return types, according to the rules for such beans. 317 * 318 * @return true if this is a proxy for an {@link MXBean}. 319 * @since 1.6 320 */ 321 public boolean isMXBean() 322 { 323 return mxBean; 324 } 325 326 /** 327 * <p> 328 * Returns a proxy for the specified bean. A proxy object is created 329 * using <code>Proxy.newProxyInstance(iface.getClassLoader(), 330 * new Class[] { iface }, handler)</code>. The 331 * {@link javax.management.NotificationEmitter} class is included as the 332 * second element of the array if <code>broadcaster</code> is true. 333 * <code>handler</code> refers to the invocation handler which forwards 334 * calls to the connection, which is created by a call to 335 * <code>new MBeanServerInvocationHandler(conn, name)</code>. 336 * </p> 337 * <p> 338 * <strong>Note</strong>: use of the proxy may result in 339 * {@link java.io.IOException}s from the underlying 340 * {@link MBeanServerConnection}. 341 * As of 1.6, the use of {@link JMX#newMBeanProxy(MBeanServerConnection, 342 * ObjectName,Class)} and {@link JMX#newMBeanProxy(MBeanServerConnection, 343 * ObjectName,Class,boolean)} is preferred. 344 * </p> 345 * 346 * @param conn the server connection to use to access the bean. 347 * @param name the {@link javax.management.ObjectName} of the 348 * bean to provide a proxy for. 349 * @param iface the interface for the bean being proxied. 350 * @param broadcaster true if the proxy should implement 351 * {@link NotificationEmitter}. 352 * @return a proxy for the specified bean. 353 * @see JMX#newMBeanProxy(MBeanServerConnection,ObjectName,Class) 354 */ 355 public static <T> T newProxyInstance(MBeanServerConnection conn, 356 ObjectName name, Class<T> iface, 357 boolean broadcaster) 358 { 359 if (broadcaster) 360 return (T) Proxy.newProxyInstance(iface.getClassLoader(), 361 new Class[] { iface, 362 NotificationEmitter.class }, 363 new MBeanServerInvocationHandler(conn,name)); 364 else 365 return (T) Proxy.newProxyInstance(iface.getClassLoader(), 366 new Class[] { iface }, 367 new MBeanServerInvocationHandler(conn,name)); 368 } 369 370 /** 371 * Returns true if the specified method is specified 372 * by one of the proxy's interfaces. 373 * 374 * @param name the name of the method to search for. 375 * @param proxyClass the class of the proxy. 376 * @param args the arguments to the method. 377 * @return true if one of the interfaces specifies the 378 * given method. 379 */ 380 private boolean inInterface(String name, Class<?> proxyClass, 381 Class<?>... args) 382 { 383 for (Class<?> iface : proxyClass.getInterfaces()) 384 { 385 try 386 { 387 iface.getMethod(name, args); 388 return true; 389 } 390 catch (NoSuchMethodException e) 391 { 392 /* Ignored; this interface doesn't specify 393 the method. */ 394 } 395 } 396 return false; 397 } 398 399 } 400