1 /*
2  * Copyright 2002-2013 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.jmx.support;
18 
19 import java.util.Collections;
20 import java.util.LinkedHashSet;
21 import java.util.Set;
22 import javax.management.InstanceAlreadyExistsException;
23 import javax.management.InstanceNotFoundException;
24 import javax.management.JMException;
25 import javax.management.MBeanServer;
26 import javax.management.ObjectInstance;
27 import javax.management.ObjectName;
28 
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 
32 import org.springframework.core.Constants;
33 
34 /**
35  * Provides supporting infrastructure for registering MBeans with an
36  * {@link javax.management.MBeanServer}. The behavior when encountering
37  * an existing MBean at a given {@link ObjectName} is fully configurable
38  * allowing for flexible registration settings.
39  *
40  * <p>All registered MBeans are tracked and can be unregistered by calling
41  * the #{@link #unregisterBeans()} method.
42  *
43  * <p>Sub-classes can receive notifications when an MBean is registered or
44  * unregistered by overriding the {@link #onRegister(ObjectName)} and
45  * {@link #onUnregister(ObjectName)} methods respectively.
46  *
47  * <p>By default, the registration process will fail if attempting to
48  * register an MBean using a {@link javax.management.ObjectName} that is
49  * already used.
50  *
51  * <p>By setting the {@link #setRegistrationBehaviorName(String) registrationBehaviorName}
52  * property to <code>REGISTRATION_IGNORE_EXISTING</code> the registration process
53  * will simply ignore existing MBeans leaving them registered. This is useful in settings
54  * where multiple applications want to share a common MBean in a shared {@link MBeanServer}.
55  *
56  * <p>Setting {@link #setRegistrationBehaviorName(String) registrationBehaviorName} property
57  * to <code>REGISTRATION_REPLACE_EXISTING</code> will cause existing MBeans to be replaced
58  * during registration if necessary. This is useful in situations where you can't guarantee
59  * the state of your {@link MBeanServer}.
60  *
61  * @author Rob Harrop
62  * @author Juergen Hoeller
63  * @since 2.0
64  * @see #setServer
65  * @see #setRegistrationBehaviorName
66  * @see org.springframework.jmx.export.MBeanExporter
67  */
68 public class MBeanRegistrationSupport {
69 
70 	/**
71 	 * Constant indicating that registration should fail when
72 	 * attempting to register an MBean under a name that already exists.
73 	 * <p>This is the default registration behavior.
74 	 */
75 	public static final int REGISTRATION_FAIL_ON_EXISTING = 0;
76 
77 	/**
78 	 * Constant indicating that registration should ignore the affected MBean
79 	 * when attempting to register an MBean under a name that already exists.
80 	 */
81 	public static final int REGISTRATION_IGNORE_EXISTING = 1;
82 
83 	/**
84 	 * Constant indicating that registration should replace the affected MBean
85 	 * when attempting to register an MBean under a name that already exists.
86 	 */
87 	public static final int REGISTRATION_REPLACE_EXISTING = 2;
88 
89 
90 	/**
91 	 * Constants for this class.
92 	 */
93 	private static final Constants constants = new Constants(MBeanRegistrationSupport.class);
94 
95 	/**
96 	 * {@code Log} instance for this class.
97 	 */
98 	protected final Log logger = LogFactory.getLog(getClass());
99 
100 	/**
101 	 * The {@code MBeanServer} instance being used to register beans.
102 	 */
103 	protected MBeanServer server;
104 
105 	/**
106 	 * The beans that have been registered by this exporter.
107 	 */
108 	protected final Set<ObjectName> registeredBeans = Collections.synchronizedSet(new LinkedHashSet<ObjectName>());
109 
110 	/**
111 	 * The action take when registering an MBean and finding that it already exists.
112 	 * By default an exception is raised.
113 	 */
114 	private int registrationBehavior = REGISTRATION_FAIL_ON_EXISTING;
115 
116 
117 	/**
118 	 * Specify the {@code MBeanServer} instance with which all beans should
119 	 * be registered. The {@code MBeanExporter} will attempt to locate an
120 	 * existing {@code MBeanServer} if none is supplied.
121 	 */
setServer(MBeanServer server)122 	public void setServer(MBeanServer server) {
123 		this.server = server;
124 	}
125 
126 	/**
127 	 * Return the {@code MBeanServer} that the beans will be registered with.
128 	 */
getServer()129 	public final MBeanServer getServer() {
130 		return this.server;
131 	}
132 
133 	/**
134 	 * Set the registration behavior by the name of the corresponding constant,
135 	 * e.g. "REGISTRATION_IGNORE_EXISTING".
136 	 * @see #setRegistrationBehavior
137 	 * @see #REGISTRATION_FAIL_ON_EXISTING
138 	 * @see #REGISTRATION_IGNORE_EXISTING
139 	 * @see #REGISTRATION_REPLACE_EXISTING
140 	 */
setRegistrationBehaviorName(String registrationBehavior)141 	public void setRegistrationBehaviorName(String registrationBehavior) {
142 		setRegistrationBehavior(constants.asNumber(registrationBehavior).intValue());
143 	}
144 
145 	/**
146 	 * Specify  what action should be taken when attempting to register an MBean
147 	 * under an {@link javax.management.ObjectName} that already exists.
148 	 * <p>Default is REGISTRATION_FAIL_ON_EXISTING.
149 	 * @see #setRegistrationBehaviorName(String)
150 	 * @see #REGISTRATION_FAIL_ON_EXISTING
151 	 * @see #REGISTRATION_IGNORE_EXISTING
152 	 * @see #REGISTRATION_REPLACE_EXISTING
153 	 */
setRegistrationBehavior(int registrationBehavior)154 	public void setRegistrationBehavior(int registrationBehavior) {
155 		this.registrationBehavior = registrationBehavior;
156 	}
157 
158 
159 	/**
160 	 * Actually register the MBean with the server. The behavior when encountering
161 	 * an existing MBean can be configured using the {@link #setRegistrationBehavior(int)}
162 	 * and {@link #setRegistrationBehaviorName(String)} methods.
163 	 * @param mbean the MBean instance
164 	 * @param objectName the suggested ObjectName for the MBean
165 	 * @throws JMException if the registration failed
166 	 */
doRegister(Object mbean, ObjectName objectName)167 	protected void doRegister(Object mbean, ObjectName objectName) throws JMException {
168 		ObjectInstance registeredBean = null;
169 		try {
170 			registeredBean = this.server.registerMBean(mbean, objectName);
171 		}
172 		catch (InstanceAlreadyExistsException ex) {
173 			if (this.registrationBehavior == REGISTRATION_IGNORE_EXISTING) {
174 				if (logger.isDebugEnabled()) {
175 					logger.debug("Ignoring existing MBean at [" + objectName + "]");
176 				}
177 			}
178 			else if (this.registrationBehavior == REGISTRATION_REPLACE_EXISTING) {
179 				try {
180 					if (logger.isDebugEnabled()) {
181 						logger.debug("Replacing existing MBean at [" + objectName + "]");
182 					}
183 					this.server.unregisterMBean(objectName);
184 					registeredBean = this.server.registerMBean(mbean, objectName);
185 				}
186 				catch (InstanceNotFoundException ex2) {
187 					logger.error("Unable to replace existing MBean at [" + objectName + "]", ex2);
188 					throw ex;
189 				}
190 			}
191 			else {
192 				throw ex;
193 			}
194 		}
195 
196 		// Track registration and notify listeners.
197 		ObjectName actualObjectName = (registeredBean != null ? registeredBean.getObjectName() : null);
198 		if (actualObjectName == null) {
199 			actualObjectName = objectName;
200 		}
201 		this.registeredBeans.add(actualObjectName);
202 		onRegister(actualObjectName, mbean);
203 	}
204 
205 	/**
206 	 * Unregisters all beans that have been registered by an instance of this class.
207 	 */
unregisterBeans()208 	protected void unregisterBeans() {
209 		for (ObjectName objectName : new LinkedHashSet<ObjectName>(this.registeredBeans)) {
210 			doUnregister(objectName);
211 		}
212 	}
213 
214 	/**
215 	 * Actually unregister the specified MBean from the server.
216 	 * @param objectName the suggested ObjectName for the MBean
217 	 */
doUnregister(ObjectName objectName)218 	protected void doUnregister(ObjectName objectName) {
219 		try {
220 			// MBean might already have been unregistered by an external process.
221 			if (this.server.isRegistered(objectName)) {
222 				this.server.unregisterMBean(objectName);
223 				onUnregister(objectName);
224 			}
225 			else {
226 				if (logger.isWarnEnabled()) {
227 					logger.warn("Could not unregister MBean [" + objectName + "] as said MBean " +
228 							"is not registered (perhaps already unregistered by an external process)");
229 				}
230 			}
231 		}
232 		catch (JMException ex) {
233 			if (logger.isErrorEnabled()) {
234 				logger.error("Could not unregister MBean [" + objectName + "]", ex);
235 			}
236 		}
237 		this.registeredBeans.remove(objectName);
238 	}
239 
240 	/**
241 	 * Return the {@link ObjectName ObjectNames} of all registered beans.
242 	 */
getRegisteredObjectNames()243 	protected final ObjectName[] getRegisteredObjectNames() {
244 		return this.registeredBeans.toArray(new ObjectName[this.registeredBeans.size()]);
245 	}
246 
247 
248 	/**
249 	 * Called when an MBean is registered under the given {@link ObjectName}. Allows
250 	 * subclasses to perform additional processing when an MBean is registered.
251 	 * <p>The default implementation delegates to {@link #onRegister(ObjectName)}.
252 	 * @param objectName the actual {@link ObjectName} that the MBean was registered with
253 	 * @param mbean the registered MBean instance
254 	 */
onRegister(ObjectName objectName, Object mbean)255 	protected void onRegister(ObjectName objectName, Object mbean) {
256 		onRegister(objectName);
257 	}
258 
259 	/**
260 	 * Called when an MBean is registered under the given {@link ObjectName}. Allows
261 	 * subclasses to perform additional processing when an MBean is registered.
262 	 * <p>The default implementation is empty. Can be overridden in subclasses.
263 	 * @param objectName the actual {@link ObjectName} that the MBean was registered with
264 	 */
onRegister(ObjectName objectName)265 	protected void onRegister(ObjectName objectName) {
266 	}
267 
268 	/**
269 	 * Called when an MBean is unregistered under the given {@link ObjectName}. Allows
270 	 * subclasses to perform additional processing when an MBean is unregistered.
271 	 * <p>The default implementation is empty. Can be overridden in subclasses.
272 	 * @param objectName the {@link ObjectName} that the MBean was registered with
273 	 */
onUnregister(ObjectName objectName)274 	protected void onUnregister(ObjectName objectName) {
275 	}
276 
277 }
278