001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jmx.support;
018
019import java.util.LinkedHashSet;
020import java.util.Set;
021import javax.management.InstanceAlreadyExistsException;
022import javax.management.InstanceNotFoundException;
023import javax.management.JMException;
024import javax.management.MBeanServer;
025import javax.management.ObjectInstance;
026import javax.management.ObjectName;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.core.Constants;
032import org.springframework.util.Assert;
033
034/**
035 * Provides supporting infrastructure for registering MBeans with an
036 * {@link javax.management.MBeanServer}. The behavior when encountering
037 * an existing MBean at a given {@link ObjectName} is fully configurable
038 * allowing for flexible registration settings.
039 *
040 * <p>All registered MBeans are tracked and can be unregistered by calling
041 * the #{@link #unregisterBeans()} method.
042 *
043 * <p>Sub-classes can receive notifications when an MBean is registered or
044 * unregistered by overriding the {@link #onRegister(ObjectName)} and
045 * {@link #onUnregister(ObjectName)} methods respectively.
046 *
047 * <p>By default, the registration process will fail if attempting to
048 * register an MBean using a {@link javax.management.ObjectName} that is
049 * already used.
050 *
051 * <p>By setting the {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy}
052 * property to {@link RegistrationPolicy#IGNORE_EXISTING} the registration process
053 * will simply ignore existing MBeans leaving them registered. This is useful in settings
054 * where multiple applications want to share a common MBean in a shared {@link MBeanServer}.
055 *
056 * <p>Setting {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy} property
057 * to {@link RegistrationPolicy#REPLACE_EXISTING} will cause existing MBeans to be replaced
058 * during registration if necessary. This is useful in situations where you can't guarantee
059 * the state of your {@link MBeanServer}.
060 *
061 * @author Rob Harrop
062 * @author Juergen Hoeller
063 * @author Phillip Webb
064 * @since 2.0
065 * @see #setServer
066 * @see #setRegistrationPolicy
067 * @see org.springframework.jmx.export.MBeanExporter
068 */
069public class MBeanRegistrationSupport {
070
071        /**
072         * Constant indicating that registration should fail when
073         * attempting to register an MBean under a name that already exists.
074         * <p>This is the default registration behavior.
075         * @deprecated since Spring 3.2, in favor of {@link RegistrationPolicy#FAIL_ON_EXISTING}
076         */
077        @Deprecated
078        public static final int REGISTRATION_FAIL_ON_EXISTING = 0;
079
080        /**
081         * Constant indicating that registration should ignore the affected MBean
082         * when attempting to register an MBean under a name that already exists.
083         * @deprecated since Spring 3.2, in favor of {@link RegistrationPolicy#IGNORE_EXISTING}
084         */
085        @Deprecated
086        public static final int REGISTRATION_IGNORE_EXISTING = 1;
087
088        /**
089         * Constant indicating that registration should replace the affected MBean
090         * when attempting to register an MBean under a name that already exists.
091         * @deprecated since Spring 3.2, in favor of {@link RegistrationPolicy#REPLACE_EXISTING}
092         */
093        @Deprecated
094        public static final int REGISTRATION_REPLACE_EXISTING = 2;
095
096
097        /**
098         * Constants for this class.
099         */
100        private static final Constants constants = new Constants(MBeanRegistrationSupport.class);
101
102        /**
103         * {@code Log} instance for this class.
104         */
105        protected final Log logger = LogFactory.getLog(getClass());
106
107        /**
108         * The {@code MBeanServer} instance being used to register beans.
109         */
110        protected MBeanServer server;
111
112        /**
113         * The beans that have been registered by this exporter.
114         */
115        private final Set<ObjectName> registeredBeans = new LinkedHashSet<ObjectName>();
116
117        /**
118         * The policy used when registering an MBean and finding that it already exists.
119         * By default an exception is raised.
120         */
121        private RegistrationPolicy registrationPolicy = RegistrationPolicy.FAIL_ON_EXISTING;
122
123
124        /**
125         * Specify the {@code MBeanServer} instance with which all beans should
126         * be registered. The {@code MBeanExporter} will attempt to locate an
127         * existing {@code MBeanServer} if none is supplied.
128         */
129        public void setServer(MBeanServer server) {
130                this.server = server;
131        }
132
133        /**
134         * Return the {@code MBeanServer} that the beans will be registered with.
135         */
136        public final MBeanServer getServer() {
137                return this.server;
138        }
139
140        /**
141         * Set the registration behavior by the name of the corresponding constant,
142         * e.g. "REGISTRATION_IGNORE_EXISTING".
143         * @see #setRegistrationBehavior
144         * @see #REGISTRATION_FAIL_ON_EXISTING
145         * @see #REGISTRATION_IGNORE_EXISTING
146         * @see #REGISTRATION_REPLACE_EXISTING
147         * @deprecated since Spring 3.2, in favor of {@link #setRegistrationPolicy(RegistrationPolicy)}
148         */
149        @Deprecated
150        public void setRegistrationBehaviorName(String registrationBehavior) {
151                setRegistrationBehavior(constants.asNumber(registrationBehavior).intValue());
152        }
153
154        /**
155         * Specify what action should be taken when attempting to register an MBean
156         * under an {@link javax.management.ObjectName} that already exists.
157         * <p>Default is REGISTRATION_FAIL_ON_EXISTING.
158         * @see #setRegistrationBehaviorName(String)
159         * @see #REGISTRATION_FAIL_ON_EXISTING
160         * @see #REGISTRATION_IGNORE_EXISTING
161         * @see #REGISTRATION_REPLACE_EXISTING
162         * @deprecated since Spring 3.2, in favor of {@link #setRegistrationPolicy(RegistrationPolicy)}
163         */
164        @Deprecated
165        public void setRegistrationBehavior(int registrationBehavior) {
166                setRegistrationPolicy(RegistrationPolicy.valueOf(registrationBehavior));
167        }
168
169        /**
170         * The policy to use when attempting to register an MBean
171         * under an {@link javax.management.ObjectName} that already exists.
172         * @param registrationPolicy the policy to use
173         * @since 3.2
174         */
175        public void setRegistrationPolicy(RegistrationPolicy registrationPolicy) {
176                Assert.notNull(registrationPolicy, "RegistrationPolicy must not be null");
177                this.registrationPolicy = registrationPolicy;
178        }
179
180
181        /**
182         * Actually register the MBean with the server. The behavior when encountering
183         * an existing MBean can be configured using the {@link #setRegistrationBehavior(int)}
184         * and {@link #setRegistrationBehaviorName(String)} methods.
185         * @param mbean the MBean instance
186         * @param objectName the suggested ObjectName for the MBean
187         * @throws JMException if the registration failed
188         */
189        protected void doRegister(Object mbean, ObjectName objectName) throws JMException {
190                ObjectName actualObjectName;
191
192                synchronized (this.registeredBeans) {
193                        ObjectInstance registeredBean = null;
194                        try {
195                                registeredBean = this.server.registerMBean(mbean, objectName);
196                        }
197                        catch (InstanceAlreadyExistsException ex) {
198                                if (this.registrationPolicy == RegistrationPolicy.IGNORE_EXISTING) {
199                                        if (logger.isDebugEnabled()) {
200                                                logger.debug("Ignoring existing MBean at [" + objectName + "]");
201                                        }
202                                }
203                                else if (this.registrationPolicy == RegistrationPolicy.REPLACE_EXISTING) {
204                                        try {
205                                                if (logger.isDebugEnabled()) {
206                                                        logger.debug("Replacing existing MBean at [" + objectName + "]");
207                                                }
208                                                this.server.unregisterMBean(objectName);
209                                                registeredBean = this.server.registerMBean(mbean, objectName);
210                                        }
211                                        catch (InstanceNotFoundException ex2) {
212                                                if (logger.isErrorEnabled()) {
213                                                        logger.error("Unable to replace existing MBean at [" + objectName + "]", ex2);
214                                                }
215                                                throw ex;
216                                        }
217                                }
218                                else {
219                                        throw ex;
220                                }
221                        }
222
223                        // Track registration and notify listeners.
224                        actualObjectName = (registeredBean != null ? registeredBean.getObjectName() : null);
225                        if (actualObjectName == null) {
226                                actualObjectName = objectName;
227                        }
228                        this.registeredBeans.add(actualObjectName);
229                }
230
231                onRegister(actualObjectName, mbean);
232        }
233
234        /**
235         * Unregisters all beans that have been registered by an instance of this class.
236         */
237        protected void unregisterBeans() {
238                Set<ObjectName> snapshot;
239                synchronized (this.registeredBeans) {
240                        snapshot = new LinkedHashSet<ObjectName>(this.registeredBeans);
241                }
242                if (!snapshot.isEmpty()) {
243                        logger.info("Unregistering JMX-exposed beans");
244                        for (ObjectName objectName : snapshot) {
245                                doUnregister(objectName);
246                        }
247                }
248        }
249
250        /**
251         * Actually unregister the specified MBean from the server.
252         * @param objectName the suggested ObjectName for the MBean
253         */
254        protected void doUnregister(ObjectName objectName) {
255                boolean actuallyUnregistered = false;
256
257                synchronized (this.registeredBeans) {
258                        if (this.registeredBeans.remove(objectName)) {
259                                try {
260                                        // MBean might already have been unregistered by an external process
261                                        if (this.server.isRegistered(objectName)) {
262                                                this.server.unregisterMBean(objectName);
263                                                actuallyUnregistered = true;
264                                        }
265                                        else {
266                                                if (logger.isWarnEnabled()) {
267                                                        logger.warn("Could not unregister MBean [" + objectName + "] as said MBean " +
268                                                                        "is not registered (perhaps already unregistered by an external process)");
269                                                }
270                                        }
271                                }
272                                catch (JMException ex) {
273                                        if (logger.isErrorEnabled()) {
274                                                logger.error("Could not unregister MBean [" + objectName + "]", ex);
275                                        }
276                                }
277                        }
278                }
279
280                if (actuallyUnregistered) {
281                        onUnregister(objectName);
282                }
283        }
284
285        /**
286         * Return the {@link ObjectName ObjectNames} of all registered beans.
287         */
288        protected final ObjectName[] getRegisteredObjectNames() {
289                synchronized (this.registeredBeans) {
290                        return this.registeredBeans.toArray(new ObjectName[this.registeredBeans.size()]);
291                }
292        }
293
294
295        /**
296         * Called when an MBean is registered under the given {@link ObjectName}. Allows
297         * subclasses to perform additional processing when an MBean is registered.
298         * <p>The default implementation delegates to {@link #onRegister(ObjectName)}.
299         * @param objectName the actual {@link ObjectName} that the MBean was registered with
300         * @param mbean the registered MBean instance
301         */
302        protected void onRegister(ObjectName objectName, Object mbean) {
303                onRegister(objectName);
304        }
305
306        /**
307         * Called when an MBean is registered under the given {@link ObjectName}. Allows
308         * subclasses to perform additional processing when an MBean is registered.
309         * <p>The default implementation is empty. Can be overridden in subclasses.
310         * @param objectName the actual {@link ObjectName} that the MBean was registered with
311         */
312        protected void onRegister(ObjectName objectName) {
313        }
314
315        /**
316         * Called when an MBean is unregistered under the given {@link ObjectName}. Allows
317         * subclasses to perform additional processing when an MBean is unregistered.
318         * <p>The default implementation is empty. Can be overridden in subclasses.
319         * @param objectName the {@link ObjectName} that the MBean was registered with
320         */
321        protected void onUnregister(ObjectName objectName) {
322        }
323
324}