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