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      // Suppress warnings as we know an instance of T will be returned.
356      @SuppressWarnings("unchecked")
357      public static <T> T newProxyInstance(MBeanServerConnection conn,
358                                           ObjectName name, Class<T> iface,
359                                           boolean broadcaster)
360      {
361        if (broadcaster)
362          return (T) Proxy.newProxyInstance(iface.getClassLoader(),
363                                            new Class[] { iface,
364                                                          NotificationEmitter.class },
365                                            new MBeanServerInvocationHandler(conn,name));
366        else
367          return (T) Proxy.newProxyInstance(iface.getClassLoader(),
368                                            new Class[] { iface },
369                                            new MBeanServerInvocationHandler(conn,name));
370      }
371    
372      /**
373       * Returns true if the specified method is specified
374       * by one of the proxy's interfaces.
375       *
376       * @param name the name of the method to search for.
377       * @param proxyClass the class of the proxy.
378       * @param args the arguments to the method.
379       * @return true if one of the interfaces specifies the
380       *         given method.
381       */
382      private boolean inInterface(String name, Class<?> proxyClass,
383                                  Class<?>... args)
384      {
385        for (Class<?> iface : proxyClass.getInterfaces())
386          {
387            try
388              {
389                iface.getMethod(name, args);
390                return true;
391              }
392            catch (NoSuchMethodException e)
393              {
394                /* Ignored; this interface doesn't specify
395                   the method. */
396              }
397          }
398        return false;
399      }
400      
401    }
402