001/*
002 * Copyright 2002-2019 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.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Properties;
023
024import javax.management.JMException;
025import javax.management.MBeanServer;
026import javax.management.MalformedObjectNameException;
027import javax.management.ObjectName;
028import javax.management.remote.JMXConnectorServer;
029import javax.management.remote.JMXConnectorServerFactory;
030import javax.management.remote.JMXServiceURL;
031import javax.management.remote.MBeanServerForwarder;
032
033import org.springframework.beans.factory.DisposableBean;
034import org.springframework.beans.factory.FactoryBean;
035import org.springframework.beans.factory.InitializingBean;
036import org.springframework.jmx.JmxException;
037import org.springframework.lang.Nullable;
038import org.springframework.util.CollectionUtils;
039
040/**
041 * {@link FactoryBean} that creates a JSR-160 {@link JMXConnectorServer},
042 * optionally registers it with the {@link MBeanServer}, and then starts it.
043 *
044 * <p>The {@code JMXConnectorServer} can be started in a separate thread by setting the
045 * {@code threaded} property to {@code true}. You can configure this thread to be a
046 * daemon thread by setting the {@code daemon} property to {@code true}.
047 *
048 * <p>The {@code JMXConnectorServer} is correctly shut down when an instance of this
049 * class is destroyed on shutdown of the containing {@code ApplicationContext}.
050 *
051 * @author Rob Harrop
052 * @author Juergen Hoeller
053 * @since 1.2
054 * @see JMXConnectorServer
055 * @see MBeanServer
056 */
057public class ConnectorServerFactoryBean extends MBeanRegistrationSupport
058                implements FactoryBean<JMXConnectorServer>, InitializingBean, DisposableBean {
059
060        /** The default service URL. */
061        public static final String DEFAULT_SERVICE_URL = "service:jmx:jmxmp://localhost:9875";
062
063
064        private String serviceUrl = DEFAULT_SERVICE_URL;
065
066        private Map<String, Object> environment = new HashMap<>();
067
068        @Nullable
069        private MBeanServerForwarder forwarder;
070
071        @Nullable
072        private ObjectName objectName;
073
074        private boolean threaded = false;
075
076        private boolean daemon = false;
077
078        @Nullable
079        private JMXConnectorServer connectorServer;
080
081
082        /**
083         * Set the service URL for the {@code JMXConnectorServer}.
084         */
085        public void setServiceUrl(String serviceUrl) {
086                this.serviceUrl = serviceUrl;
087        }
088
089        /**
090         * Set the environment properties used to construct the {@code JMXConnectorServer}
091         * as {@code java.util.Properties} (String key/value pairs).
092         */
093        public void setEnvironment(@Nullable Properties environment) {
094                CollectionUtils.mergePropertiesIntoMap(environment, this.environment);
095        }
096
097        /**
098         * Set the environment properties used to construct the {@code JMXConnector}
099         * as a {@code Map} of String keys and arbitrary Object values.
100         */
101        public void setEnvironmentMap(@Nullable Map<String, ?> environment) {
102                if (environment != null) {
103                        this.environment.putAll(environment);
104                }
105        }
106
107        /**
108         * Set an MBeanServerForwarder to be applied to the {@code JMXConnectorServer}.
109         */
110        public void setForwarder(MBeanServerForwarder forwarder) {
111                this.forwarder = forwarder;
112        }
113
114        /**
115         * Set the {@code ObjectName} used to register the {@code JMXConnectorServer}
116         * itself with the {@code MBeanServer}, as {@code ObjectName} instance
117         * or as {@code String}.
118         * @throws MalformedObjectNameException if the {@code ObjectName} is malformed
119         */
120        public void setObjectName(Object objectName) throws MalformedObjectNameException {
121                this.objectName = ObjectNameManager.getInstance(objectName);
122        }
123
124        /**
125         * Set whether the {@code JMXConnectorServer} should be started in a separate thread.
126         */
127        public void setThreaded(boolean threaded) {
128                this.threaded = threaded;
129        }
130
131        /**
132         * Set whether any threads started for the {@code JMXConnectorServer} should be
133         * started as daemon threads.
134         */
135        public void setDaemon(boolean daemon) {
136                this.daemon = daemon;
137        }
138
139
140        /**
141         * Start the connector server. If the {@code threaded} flag is set to {@code true},
142         * the {@code JMXConnectorServer} will be started in a separate thread.
143         * If the {@code daemon} flag is set to {@code true}, that thread will be
144         * started as a daemon thread.
145         * @throws JMException if a problem occurred when registering the connector server
146         * with the {@code MBeanServer}
147         * @throws IOException if there is a problem starting the connector server
148         */
149        @Override
150        public void afterPropertiesSet() throws JMException, IOException {
151                if (this.server == null) {
152                        this.server = JmxUtils.locateMBeanServer();
153                }
154
155                // Create the JMX service URL.
156                JMXServiceURL url = new JMXServiceURL(this.serviceUrl);
157
158                // Create the connector server now.
159                this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, this.environment, this.server);
160
161                // Set the given MBeanServerForwarder, if any.
162                if (this.forwarder != null) {
163                        this.connectorServer.setMBeanServerForwarder(this.forwarder);
164                }
165
166                // Do we want to register the connector with the MBean server?
167                if (this.objectName != null) {
168                        doRegister(this.connectorServer, this.objectName);
169                }
170
171                try {
172                        if (this.threaded) {
173                                // Start the connector server asynchronously (in a separate thread).
174                                final JMXConnectorServer serverToStart = this.connectorServer;
175                                Thread connectorThread = new Thread() {
176                                        @Override
177                                        public void run() {
178                                                try {
179                                                        serverToStart.start();
180                                                }
181                                                catch (IOException ex) {
182                                                        throw new JmxException("Could not start JMX connector server after delay", ex);
183                                                }
184                                        }
185                                };
186
187                                connectorThread.setName("JMX Connector Thread [" + this.serviceUrl + "]");
188                                connectorThread.setDaemon(this.daemon);
189                                connectorThread.start();
190                        }
191                        else {
192                                // Start the connector server in the same thread.
193                                this.connectorServer.start();
194                        }
195
196                        if (logger.isInfoEnabled()) {
197                                logger.info("JMX connector server started: " + this.connectorServer);
198                        }
199                }
200
201                catch (IOException ex) {
202                        // Unregister the connector server if startup failed.
203                        unregisterBeans();
204                        throw ex;
205                }
206        }
207
208
209        @Override
210        @Nullable
211        public JMXConnectorServer getObject() {
212                return this.connectorServer;
213        }
214
215        @Override
216        public Class<? extends JMXConnectorServer> getObjectType() {
217                return (this.connectorServer != null ? this.connectorServer.getClass() : JMXConnectorServer.class);
218        }
219
220        @Override
221        public boolean isSingleton() {
222                return true;
223        }
224
225
226        /**
227         * Stop the {@code JMXConnectorServer} managed by an instance of this class.
228         * Automatically called on {@code ApplicationContext} shutdown.
229         * @throws IOException if there is an error stopping the connector server
230         */
231        @Override
232        public void destroy() throws IOException {
233                try {
234                        if (this.connectorServer != null) {
235                                if (logger.isInfoEnabled()) {
236                                        logger.info("Stopping JMX connector server: " + this.connectorServer);
237                                }
238                                this.connectorServer.stop();
239                        }
240                }
241                finally {
242                        unregisterBeans();
243                }
244        }
245
246}