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;
021
022import javax.management.InstanceAlreadyExistsException;
023import javax.management.InstanceNotFoundException;
024import javax.management.JMException;
025import javax.management.MBeanServer;
026import javax.management.ObjectInstance;
027import javax.management.ObjectName;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032import org.springframework.lang.Nullable;
033import org.springframework.util.Assert;
034
035/**
036 * Provides supporting infrastructure for registering MBeans with an
037 * {@link javax.management.MBeanServer}. The behavior when encountering
038 * an existing MBean at a given {@link ObjectName} is fully configurable
039 * allowing for flexible registration settings.
040 *
041 * <p>All registered MBeans are tracked and can be unregistered by calling
042 * the #{@link #unregisterBeans()} method.
043 *
044 * <p>Sub-classes can receive notifications when an MBean is registered or
045 * unregistered by overriding the {@link #onRegister(ObjectName)} and
046 * {@link #onUnregister(ObjectName)} methods respectively.
047 *
048 * <p>By default, the registration process will fail if attempting to
049 * register an MBean using a {@link javax.management.ObjectName} that is
050 * already used.
051 *
052 * <p>By setting the {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy}
053 * property to {@link RegistrationPolicy#IGNORE_EXISTING} the registration process
054 * will simply ignore existing MBeans leaving them registered. This is useful in settings
055 * where multiple applications want to share a common MBean in a shared {@link MBeanServer}.
056 *
057 * <p>Setting {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy} property
058 * to {@link RegistrationPolicy#REPLACE_EXISTING} will cause existing MBeans to be replaced
059 * during registration if necessary. This is useful in situations where you can't guarantee
060 * the state of your {@link MBeanServer}.
061 *
062 * @author Rob Harrop
063 * @author Juergen Hoeller
064 * @author Phillip Webb
065 * @since 2.0
066 * @see #setServer
067 * @see #setRegistrationPolicy
068 * @see org.springframework.jmx.export.MBeanExporter
069 */
070public class MBeanRegistrationSupport {
071
072        /**
073         * {@code Log} instance for this class.
074         */
075        protected final Log logger = LogFactory.getLog(getClass());
076
077        /**
078         * The {@code MBeanServer} instance being used to register beans.
079         */
080        @Nullable
081        protected MBeanServer server;
082
083        /**
084         * The beans that have been registered by this exporter.
085         */
086        private final Set<ObjectName> registeredBeans = new LinkedHashSet<>();
087
088        /**
089         * The policy used when registering an MBean and finding that it already exists.
090         * By default an exception is raised.
091         */
092        private RegistrationPolicy registrationPolicy = RegistrationPolicy.FAIL_ON_EXISTING;
093
094
095        /**
096         * Specify the {@code MBeanServer} instance with which all beans should
097         * be registered. The {@code MBeanExporter} will attempt to locate an
098         * existing {@code MBeanServer} if none is supplied.
099         */
100        public void setServer(@Nullable MBeanServer server) {
101                this.server = server;
102        }
103
104        /**
105         * Return the {@code MBeanServer} that the beans will be registered with.
106         */
107        @Nullable
108        public final MBeanServer getServer() {
109                return this.server;
110        }
111
112        /**
113         * The policy to use when attempting to register an MBean
114         * under an {@link javax.management.ObjectName} that already exists.
115         * @param registrationPolicy the policy to use
116         * @since 3.2
117         */
118        public void setRegistrationPolicy(RegistrationPolicy registrationPolicy) {
119                Assert.notNull(registrationPolicy, "RegistrationPolicy must not be null");
120                this.registrationPolicy = registrationPolicy;
121        }
122
123
124        /**
125         * Actually register the MBean with the server. The behavior when encountering
126         * an existing MBean can be configured using {@link #setRegistrationPolicy}.
127         * @param mbean the MBean instance
128         * @param objectName the suggested ObjectName for the MBean
129         * @throws JMException if the registration failed
130         */
131        protected void doRegister(Object mbean, ObjectName objectName) throws JMException {
132                Assert.state(this.server != null, "No MBeanServer set");
133                ObjectName actualObjectName;
134
135                synchronized (this.registeredBeans) {
136                        ObjectInstance registeredBean = null;
137                        try {
138                                registeredBean = this.server.registerMBean(mbean, objectName);
139                        }
140                        catch (InstanceAlreadyExistsException ex) {
141                                if (this.registrationPolicy == RegistrationPolicy.IGNORE_EXISTING) {
142                                        if (logger.isDebugEnabled()) {
143                                                logger.debug("Ignoring existing MBean at [" + objectName + "]");
144                                        }
145                                }
146                                else if (this.registrationPolicy == RegistrationPolicy.REPLACE_EXISTING) {
147                                        try {
148                                                if (logger.isDebugEnabled()) {
149                                                        logger.debug("Replacing existing MBean at [" + objectName + "]");
150                                                }
151                                                this.server.unregisterMBean(objectName);
152                                                registeredBean = this.server.registerMBean(mbean, objectName);
153                                        }
154                                        catch (InstanceNotFoundException ex2) {
155                                                if (logger.isInfoEnabled()) {
156                                                        logger.info("Unable to replace existing MBean at [" + objectName + "]", ex2);
157                                                }
158                                                throw ex;
159                                        }
160                                }
161                                else {
162                                        throw ex;
163                                }
164                        }
165
166                        // Track registration and notify listeners.
167                        actualObjectName = (registeredBean != null ? registeredBean.getObjectName() : null);
168                        if (actualObjectName == null) {
169                                actualObjectName = objectName;
170                        }
171                        this.registeredBeans.add(actualObjectName);
172                }
173
174                onRegister(actualObjectName, mbean);
175        }
176
177        /**
178         * Unregisters all beans that have been registered by an instance of this class.
179         */
180        protected void unregisterBeans() {
181                Set<ObjectName> snapshot;
182                synchronized (this.registeredBeans) {
183                        snapshot = new LinkedHashSet<>(this.registeredBeans);
184                }
185                if (!snapshot.isEmpty()) {
186                        logger.debug("Unregistering JMX-exposed beans");
187                        for (ObjectName objectName : snapshot) {
188                                doUnregister(objectName);
189                        }
190                }
191        }
192
193        /**
194         * Actually unregister the specified MBean from the server.
195         * @param objectName the suggested ObjectName for the MBean
196         */
197        protected void doUnregister(ObjectName objectName) {
198                Assert.state(this.server != null, "No MBeanServer set");
199                boolean actuallyUnregistered = false;
200
201                synchronized (this.registeredBeans) {
202                        if (this.registeredBeans.remove(objectName)) {
203                                try {
204                                        // MBean might already have been unregistered by an external process
205                                        if (this.server.isRegistered(objectName)) {
206                                                this.server.unregisterMBean(objectName);
207                                                actuallyUnregistered = true;
208                                        }
209                                        else {
210                                                if (logger.isInfoEnabled()) {
211                                                        logger.info("Could not unregister MBean [" + objectName + "] as said MBean " +
212                                                                        "is not registered (perhaps already unregistered by an external process)");
213                                                }
214                                        }
215                                }
216                                catch (JMException ex) {
217                                        if (logger.isInfoEnabled()) {
218                                                logger.info("Could not unregister MBean [" + objectName + "]", ex);
219                                        }
220                                }
221                        }
222                }
223
224                if (actuallyUnregistered) {
225                        onUnregister(objectName);
226                }
227        }
228
229        /**
230         * Return the {@link ObjectName ObjectNames} of all registered beans.
231         */
232        protected final ObjectName[] getRegisteredObjectNames() {
233                synchronized (this.registeredBeans) {
234                        return this.registeredBeans.toArray(new ObjectName[0]);
235                }
236        }
237
238
239        /**
240         * Called when an MBean is registered under the given {@link ObjectName}. Allows
241         * subclasses to perform additional processing when an MBean is registered.
242         * <p>The default implementation delegates to {@link #onRegister(ObjectName)}.
243         * @param objectName the actual {@link ObjectName} that the MBean was registered with
244         * @param mbean the registered MBean instance
245         */
246        protected void onRegister(ObjectName objectName, Object mbean) {
247                onRegister(objectName);
248        }
249
250        /**
251         * Called when an MBean is registered under the given {@link ObjectName}. Allows
252         * subclasses to perform additional processing when an MBean is registered.
253         * <p>The default implementation is empty. Can be overridden in subclasses.
254         * @param objectName the actual {@link ObjectName} that the MBean was registered with
255         */
256        protected void onRegister(ObjectName objectName) {
257        }
258
259        /**
260         * Called when an MBean is unregistered under the given {@link ObjectName}. Allows
261         * subclasses to perform additional processing when an MBean is unregistered.
262         * <p>The default implementation is empty. Can be overridden in subclasses.
263         * @param objectName the {@link ObjectName} that the MBean was registered with
264         */
265        protected void onUnregister(ObjectName objectName) {
266        }
267
268}