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