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 }