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.net.MalformedURLException;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Properties;
024
025import javax.management.MBeanServerConnection;
026import javax.management.remote.JMXConnector;
027import javax.management.remote.JMXConnectorFactory;
028import javax.management.remote.JMXServiceURL;
029
030import org.springframework.aop.TargetSource;
031import org.springframework.aop.framework.ProxyFactory;
032import org.springframework.aop.target.AbstractLazyCreationTargetSource;
033import org.springframework.beans.factory.BeanClassLoaderAware;
034import org.springframework.beans.factory.DisposableBean;
035import org.springframework.beans.factory.FactoryBean;
036import org.springframework.beans.factory.InitializingBean;
037import org.springframework.lang.Nullable;
038import org.springframework.util.Assert;
039import org.springframework.util.ClassUtils;
040import org.springframework.util.CollectionUtils;
041
042/**
043 * {@link FactoryBean} that creates a JMX 1.2 {@code MBeanServerConnection}
044 * to a remote {@code MBeanServer} exposed via a {@code JMXServerConnector}.
045 * Exposes the {@code MBeanServer} for bean references.
046 *
047 * @author Rob Harrop
048 * @author Juergen Hoeller
049 * @since 1.2
050 * @see MBeanServerFactoryBean
051 * @see ConnectorServerFactoryBean
052 * @see org.springframework.jmx.access.MBeanClientInterceptor#setServer
053 * @see org.springframework.jmx.access.NotificationListenerRegistrar#setServer
054 */
055public class MBeanServerConnectionFactoryBean
056                implements FactoryBean<MBeanServerConnection>, BeanClassLoaderAware, InitializingBean, DisposableBean {
057
058        @Nullable
059        private JMXServiceURL serviceUrl;
060
061        private Map<String, Object> environment = new HashMap<>();
062
063        private boolean connectOnStartup = true;
064
065        @Nullable
066        private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
067
068        @Nullable
069        private JMXConnector connector;
070
071        @Nullable
072        private MBeanServerConnection connection;
073
074        @Nullable
075        private JMXConnectorLazyInitTargetSource connectorTargetSource;
076
077
078        /**
079         * Set the service URL of the remote {@code MBeanServer}.
080         */
081        public void setServiceUrl(String url) throws MalformedURLException {
082                this.serviceUrl = new JMXServiceURL(url);
083        }
084
085        /**
086         * Set the environment properties used to construct the {@code JMXConnector}
087         * as {@code java.util.Properties} (String key/value pairs).
088         */
089        public void setEnvironment(Properties environment) {
090                CollectionUtils.mergePropertiesIntoMap(environment, this.environment);
091        }
092
093        /**
094         * Set the environment properties used to construct the {@code JMXConnector}
095         * as a {@code Map} of String keys and arbitrary Object values.
096         */
097        public void setEnvironmentMap(@Nullable Map<String, ?> environment) {
098                if (environment != null) {
099                        this.environment.putAll(environment);
100                }
101        }
102
103        /**
104         * Set whether to connect to the server on startup.
105         * <p>Default is {@code true}.
106         * <p>Can be turned off to allow for late start of the JMX server.
107         * In this case, the JMX connector will be fetched on first access.
108         */
109        public void setConnectOnStartup(boolean connectOnStartup) {
110                this.connectOnStartup = connectOnStartup;
111        }
112
113        @Override
114        public void setBeanClassLoader(ClassLoader classLoader) {
115                this.beanClassLoader = classLoader;
116        }
117
118
119        /**
120         * Creates a {@code JMXConnector} for the given settings
121         * and exposes the associated {@code MBeanServerConnection}.
122         */
123        @Override
124        public void afterPropertiesSet() throws IOException {
125                if (this.serviceUrl == null) {
126                        throw new IllegalArgumentException("Property 'serviceUrl' is required");
127                }
128
129                if (this.connectOnStartup) {
130                        connect();
131                }
132                else {
133                        createLazyConnection();
134                }
135        }
136
137        /**
138         * Connects to the remote {@code MBeanServer} using the configured service URL and
139         * environment properties.
140         */
141        private void connect() throws IOException {
142                Assert.state(this.serviceUrl != null, "No JMXServiceURL set");
143                this.connector = JMXConnectorFactory.connect(this.serviceUrl, this.environment);
144                this.connection = this.connector.getMBeanServerConnection();
145        }
146
147        /**
148         * Creates lazy proxies for the {@code JMXConnector} and {@code MBeanServerConnection}.
149         */
150        private void createLazyConnection() {
151                this.connectorTargetSource = new JMXConnectorLazyInitTargetSource();
152                TargetSource connectionTargetSource = new MBeanServerConnectionLazyInitTargetSource();
153
154                this.connector = (JMXConnector)
155                                new ProxyFactory(JMXConnector.class, this.connectorTargetSource).getProxy(this.beanClassLoader);
156                this.connection = (MBeanServerConnection)
157                                new ProxyFactory(MBeanServerConnection.class, connectionTargetSource).getProxy(this.beanClassLoader);
158        }
159
160
161        @Override
162        @Nullable
163        public MBeanServerConnection getObject() {
164                return this.connection;
165        }
166
167        @Override
168        public Class<? extends MBeanServerConnection> getObjectType() {
169                return (this.connection != null ? this.connection.getClass() : MBeanServerConnection.class);
170        }
171
172        @Override
173        public boolean isSingleton() {
174                return true;
175        }
176
177
178        /**
179         * Closes the underlying {@code JMXConnector}.
180         */
181        @Override
182        public void destroy() throws IOException {
183                if (this.connector != null &&
184                                (this.connectorTargetSource == null || this.connectorTargetSource.isInitialized())) {
185                        this.connector.close();
186                }
187        }
188
189
190        /**
191         * Lazily creates a {@code JMXConnector} using the configured service URL
192         * and environment properties.
193         * @see MBeanServerConnectionFactoryBean#setServiceUrl(String)
194         * @see MBeanServerConnectionFactoryBean#setEnvironment(java.util.Properties)
195         */
196        private class JMXConnectorLazyInitTargetSource extends AbstractLazyCreationTargetSource {
197
198                @Override
199                protected Object createObject() throws Exception {
200                        Assert.state(serviceUrl != null, "No JMXServiceURL set");
201                        return JMXConnectorFactory.connect(serviceUrl, environment);
202                }
203
204                @Override
205                public Class<?> getTargetClass() {
206                        return JMXConnector.class;
207                }
208        }
209
210
211        /**
212         * Lazily creates an {@code MBeanServerConnection}.
213         */
214        private class MBeanServerConnectionLazyInitTargetSource extends AbstractLazyCreationTargetSource {
215
216                @Override
217                protected Object createObject() throws Exception {
218                        Assert.state(connector != null, "JMXConnector not initialized");
219                        return connector.getMBeanServerConnection();
220                }
221
222                @Override
223                public Class<?> getTargetClass() {
224                        return MBeanServerConnection.class;
225                }
226        }
227
228}