View Javadoc
1   package com.atlassian.plugin.jmx;
2   
3   import com.google.common.annotations.VisibleForTesting;
4   
5   import javax.management.ObjectName;
6   import java.lang.ref.WeakReference;
7   import java.lang.reflect.InvocationHandler;
8   import java.lang.reflect.Method;
9   import java.lang.reflect.Proxy;
10  
11  /**
12   * An base class for managing registration and unregistration of a JMX MXBean.
13   * <p>
14   * This implementation keeps a weak reference to the underlying implementation so that failing to unregister from the platform MBean
15   * server does not leak implementation instances. In the event of usage after the implementation is garbage collected, an {@link
16   * IllegalStateException} is thrown.
17   * <p>
18   * The idiomatic usage of this class is to subclass, pass the MXBean interface type as parameter, directly implement the MXBean
19   * using a strong reference to the required system components, and override {@link AbstractJmxBridge#getMXBean} to return this. The
20   * {@link AbstractJmxBridge#register()} method will construct a proxy which weakly references this implementation.
21   *
22   * @since v3.0.24
23   */
24  public abstract class AbstractJmxBridge<MXBean> {
25      private final ObjectName objectName;
26      private final Class<MXBean> mxBeanClass;
27  
28      /**
29       * Construct given JMX ObjectName and interface class.
30       *
31       * @param objectName  the ObjectName to register under.
32       * @param mxBeanClass the interface class for the MXBean.
33       */
34      public AbstractJmxBridge(final ObjectName objectName, final Class<MXBean> mxBeanClass) {
35          this.objectName = objectName;
36          this.mxBeanClass = mxBeanClass;
37      }
38  
39      /**
40       * Obtain the actual MXBean implementation.
41       * <p>
42       * The return value is weakly held.
43       *
44       * @return The implementation of the MXBean to expose via JMX.
45       */
46      protected abstract MXBean getMXBean();
47  
48      /**
49       * Register the MXBean with the platform MBean server.
50       */
51      public void register() {
52          registerInternal();
53      }
54  
55  
56      public ObjectName getObjectName() {
57          return objectName;
58      }
59  
60      @VisibleForTesting
61      WeakMXBeanInvocationHandler<MXBean> registerInternal() {
62          final WeakMXBeanInvocationHandler<MXBean> handler = new WeakMXBeanInvocationHandler<MXBean>(objectName, getMXBean());
63          final Object proxy = Proxy.newProxyInstance(mxBeanClass.getClassLoader(), new Class[]{mxBeanClass}, handler);
64          JmxUtil.register(proxy, objectName);
65          return handler;
66      }
67  
68      /**
69       * Unregister the MXBean from the platform MBean server.
70       */
71      public void unregister() {
72          JmxUtil.unregister(objectName);
73      }
74  
75      /**
76       * An InvocationHandler which weakly holds an MXBean and forwards requests to it, defaulting if it is discarded.
77       * <p>
78       * This inner class is static, and must not retain references to the outer class, either explicit or implicit. Instances of
79       * {@link Proxy} which hold this InvocationHandler are registered with JMX, and the weak reference logic only works with the
80       * simple idiomatic usage of {@link AbstractJmxBridge} if we do not hold outer references.
81       */
82      @VisibleForTesting
83      static class WeakMXBeanInvocationHandler<MXBean> implements InvocationHandler {
84          private final ObjectName objectName;
85          private final WeakReference<MXBean> implementationReference;
86  
87          /**
88           * Construct given implementation.
89           * <p>
90           * The implementation is weakly held so that registration of this as an MXBean does not stop garbage collection.
91           *
92           * @param objectName     the object name of the MXBean which is used in exception messages.
93           * @param implementation the MXBean implementation to forward calls to, weakly held.
94           */
95          public WeakMXBeanInvocationHandler(final ObjectName objectName, final MXBean implementation) {
96              this.objectName = objectName;
97              this.implementationReference = new WeakReference<>(implementation);
98          }
99  
100         @VisibleForTesting
101         WeakReference<MXBean> getImplementationReference() {
102             return implementationReference;
103         }
104 
105         @Override
106         public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
107             final MXBean implementation = implementationReference.get();
108             if (null == implementation) {
109                 // We want to unregister in case our interface classes come from classloaders that need collection.
110                 JmxUtil.unregister(objectName);
111                 throw new IllegalStateException("Cannot use stale MXBean '" + objectName + "'");
112             } else {
113                 return method.invoke(implementation, args);
114             }
115         }
116     }
117 }