001 /*
002 * Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.management.remote;
027
028 import com.sun.jmx.remote.util.ClassLogger;
029 import com.sun.jmx.remote.util.EnvHelp;
030
031 import java.io.IOException;
032 import java.net.MalformedURLException;
033 import java.util.Collections;
034 import java.util.HashMap;
035 import java.util.Iterator;
036 import java.util.Map;
037 import java.util.ServiceLoader;
038
039 import javax.management.MBeanServer;
040 import javax.management.ObjectName;
041
042 /**
043 * <p>Factory to create JMX API connector servers. There
044 * are no instances of this class.</p>
045 *
046 * <p>Each connector server is created by an instance of {@link
047 * JMXConnectorServerProvider}. This instance is found as follows. Suppose
048 * the given {@link JMXServiceURL} looks like
049 * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
050 * Then the factory will attempt to find the appropriate {@link
051 * JMXConnectorServerProvider} for <code><em>protocol</em></code>. Each
052 * occurrence of the character <code>+</code> or <code>-</code> in
053 * <code><em>protocol</em></code> is replaced by <code>.</code> or
054 * <code>_</code>, respectively.</p>
055 *
056 * <p>A <em>provider package list</em> is searched for as follows:</p>
057 *
058 * <ol>
059 *
060 * <li>If the <code>environment</code> parameter to {@link
061 * #newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)
062 * newJMXConnectorServer} contains the key
063 * <code>jmx.remote.protocol.provider.pkgs</code> then the associated
064 * value is the provider package list.
065 *
066 * <li>Otherwise, if the system property
067 * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
068 * is the provider package list.
069 *
070 * <li>Otherwise, there is no provider package list.
071 *
072 * </ol>
073 *
074 * <p>The provider package list is a string that is interpreted as a
075 * list of non-empty Java package names separated by vertical bars
076 * (<code>|</code>). If the string is empty, then so is the provider
077 * package list. If the provider package list is not a String, or if
078 * it contains an element that is an empty string, a {@link
079 * JMXProviderException} is thrown.</p>
080 *
081 * <p>If the provider package list exists and is not empty, then for
082 * each element <code><em>pkg</em></code> of the list, the factory
083 * will attempt to load the class
084 *
085 * <blockquote>
086 * <code><em>pkg</em>.<em>protocol</em>.ServerProvider</code>
087 * </blockquote>
088
089 * <p>If the <code>environment</code> parameter to {@link
090 * #newJMXConnectorServer(JMXServiceURL, Map, MBeanServer)
091 * newJMXConnectorServer} contains the key
092 * <code>jmx.remote.protocol.provider.class.loader</code> then the
093 * associated value is the class loader to use to load the provider.
094 * If the associated value is not an instance of {@link
095 * java.lang.ClassLoader}, an {@link
096 * java.lang.IllegalArgumentException} is thrown.</p>
097 *
098 * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
099 * key is not present in the <code>environment</code> parameter, the
100 * calling thread's context class loader is used.</p>
101 *
102 * <p>If the attempt to load this class produces a {@link
103 * ClassNotFoundException}, the search for a handler continues with
104 * the next element of the list.</p>
105 *
106 * <p>Otherwise, a problem with the provider found is signalled by a
107 * {@link JMXProviderException} whose {@link
108 * JMXProviderException#getCause() <em>cause</em>} indicates the
109 * underlying exception, as follows:</p>
110 *
111 * <ul>
112 *
113 * <li>if the attempt to load the class produces an exception other
114 * than <code>ClassNotFoundException</code>, that is the
115 * <em>cause</em>;
116 *
117 * <li>if {@link Class#newInstance()} for the class produces an
118 * exception, that is the <em>cause</em>.
119 *
120 * </ul>
121 *
122 * <p>If no provider is found by the above steps, including the
123 * default case where there is no provider package list, then the
124 * implementation will use its own provider for
125 * <code><em>protocol</em></code>, or it will throw a
126 * <code>MalformedURLException</code> if there is none. An
127 * implementation may choose to find providers by other means. For
128 * example, it may support the <a
129 * href="{@docRoot}/../technotes/guides/jar/jar.html#Service Provider">
130 * JAR conventions for service providers</a>, where the service
131 * interface is <code>JMXConnectorServerProvider</code>.</p>
132 *
133 * <p>Every implementation must support the RMI connector protocols,
134 * specified with the string <code>rmi</code> or
135 * <code>iiop</code>.</p>
136 *
137 * <p>Once a provider is found, the result of the
138 * <code>newJMXConnectorServer</code> method is the result of calling
139 * {@link
140 * JMXConnectorServerProvider#newJMXConnectorServer(JMXServiceURL,
141 * Map, MBeanServer) newJMXConnectorServer} on the provider.</p>
142 *
143 * <p>The <code>Map</code> parameter passed to the
144 * <code>JMXConnectorServerProvider</code> is a new read-only
145 * <code>Map</code> that contains all the entries that were in the
146 * <code>environment</code> parameter to {@link
147 * #newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)
148 * JMXConnectorServerFactory.newJMXConnectorServer}, if there was one.
149 * Additionally, if the
150 * <code>jmx.remote.protocol.provider.class.loader</code> key is not
151 * present in the <code>environment</code> parameter, it is added to
152 * the new read-only <code>Map</code>. The associated value is the
153 * calling thread's context class loader.</p>
154 *
155 * @since 1.5
156 */
157 public class JMXConnectorServerFactory {
158
159 /**
160 * <p>Name of the attribute that specifies the default class
161 * loader. This class loader is used to deserialize objects in
162 * requests received from the client, possibly after consulting an
163 * MBean-specific class loader. The value associated with this
164 * attribute is an instance of {@link ClassLoader}.</p>
165 */
166 public static final String DEFAULT_CLASS_LOADER = JMXConnectorFactory.DEFAULT_CLASS_LOADER;
167
168 /**
169 * <p>Name of the attribute that specifies the default class
170 * loader MBean name. This class loader is used to deserialize objects in
171 * requests received from the client, possibly after consulting an
172 * MBean-specific class loader. The value associated with this
173 * attribute is an instance of {@link ObjectName}.</p>
174 */
175 public static final String DEFAULT_CLASS_LOADER_NAME = "jmx.remote.default.class.loader.name";
176
177 /**
178 * <p>Name of the attribute that specifies the provider packages
179 * that are consulted when looking for the handler for a protocol.
180 * The value associated with this attribute is a string with
181 * package names separated by vertical bars (<code>|</code>).</p>
182 */
183 public static final String PROTOCOL_PROVIDER_PACKAGES = "jmx.remote.protocol.provider.pkgs";
184
185 /**
186 * <p>Name of the attribute that specifies the class
187 * loader for loading protocol providers.
188 * The value associated with this attribute is an instance
189 * of {@link ClassLoader}.</p>
190 */
191 public static final String PROTOCOL_PROVIDER_CLASS_LOADER = "jmx.remote.protocol.provider.class.loader";
192
193 private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE = "com.sun.jmx.remote.protocol";
194
195 private static final ClassLogger logger = new ClassLogger(
196 "javax.management.remote.misc", "JMXConnectorServerFactory");
197
198 /** There are no instances of this class. */
199 private JMXConnectorServerFactory() {
200 }
201
202 private static JMXConnectorServer getConnectorServerAsService(
203 ClassLoader loader, JMXServiceURL url, Map<String, ?> map,
204 MBeanServer mbs) throws IOException {
205 Iterator<JMXConnectorServerProvider> providers = JMXConnectorFactory
206 .getProviderIterator(JMXConnectorServerProvider.class,
207 loader);
208
209 JMXConnectorServer connection = null;
210 IOException exception = null;
211 while (providers.hasNext()) {
212 try {
213 connection = providers.next().newJMXConnectorServer(
214 url, map, mbs);
215 return connection;
216 } catch (JMXProviderException e) {
217 throw e;
218 } catch (Exception e) {
219 if (logger.traceOn())
220 logger.trace("getConnectorAsService", "URL[" + url
221 + "] Service provider exception: " + e);
222 if (!(e instanceof MalformedURLException)) {
223 if (exception == null) {
224 if (exception instanceof IOException) {
225 exception = (IOException) e;
226 } else {
227 exception = EnvHelp.initCause(
228 new IOException(e.getMessage()), e);
229 }
230 }
231 }
232 continue;
233 }
234 }
235 if (exception == null)
236 return null;
237 else
238 throw exception;
239 }
240
241 /**
242 * <p>Creates a connector server at the given address. The
243 * resultant server is not started until its {@link
244 * JMXConnectorServer#start() start} method is called.</p>
245 *
246 * @param serviceURL the address of the new connector server. The
247 * actual address of the new connector server, as returned by its
248 * {@link JMXConnectorServer#getAddress() getAddress} method, will
249 * not necessarily be exactly the same. For example, it might
250 * include a port number if the original address did not.
251 *
252 * @param environment a set of attributes to control the new
253 * connector server's behavior. This parameter can be null.
254 * Keys in this map must be Strings. The appropriate type of each
255 * associated value depends on the attribute. The contents of
256 * <code>environment</code> are not changed by this call.
257 *
258 * @param mbeanServer the MBean server that this connector server
259 * is attached to. Null if this connector server will be attached
260 * to an MBean server by being registered in it.
261 *
262 * @return a <code>JMXConnectorServer</code> representing the new
263 * connector server. Each successful call to this method produces
264 * a different object.
265 *
266 * @exception NullPointerException if <code>serviceURL</code> is null.
267 *
268 * @exception IOException if the connector server cannot be made
269 * because of a communication problem.
270 *
271 * @exception MalformedURLException if there is no provider for the
272 * protocol in <code>serviceURL</code>.
273 *
274 * @exception JMXProviderException if there is a provider for the
275 * protocol in <code>serviceURL</code> but it cannot be used for
276 * some reason.
277 */
278 public static JMXConnectorServer newJMXConnectorServer(
279 JMXServiceURL serviceURL, Map<String, ?> environment,
280 MBeanServer mbeanServer) throws IOException {
281 Map<String, Object> envcopy;
282 if (environment == null)
283 envcopy = new HashMap<String, Object>();
284 else {
285 EnvHelp.checkAttributes(environment);
286 envcopy = new HashMap<String, Object>(environment);
287 }
288
289 final Class<JMXConnectorServerProvider> targetInterface = JMXConnectorServerProvider.class;
290 final ClassLoader loader = JMXConnectorFactory
291 .resolveClassLoader(envcopy);
292 final String protocol = serviceURL.getProtocol();
293 final String providerClassName = "ServerProvider";
294
295 JMXConnectorServerProvider provider = JMXConnectorFactory
296 .getProvider(serviceURL, envcopy, providerClassName,
297 targetInterface, loader);
298
299 IOException exception = null;
300 if (provider == null) {
301 // Loader is null when context class loader is set to null
302 // and no loader has been provided in map.
303 // com.sun.jmx.remote.util.Service class extracted from j2se
304 // provider search algorithm doesn't handle well null classloader.
305 if (loader != null) {
306 try {
307 JMXConnectorServer connection = getConnectorServerAsService(
308 loader, serviceURL, envcopy, mbeanServer);
309 if (connection != null)
310 return connection;
311 } catch (JMXProviderException e) {
312 throw e;
313 } catch (IOException e) {
314 exception = e;
315 }
316 }
317 provider = JMXConnectorFactory.getProvider(protocol,
318 PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
319 JMXConnectorFactory.class.getClassLoader(),
320 providerClassName, targetInterface);
321 }
322
323 if (provider == null) {
324 MalformedURLException e = new MalformedURLException(
325 "Unsupported protocol: " + protocol);
326 if (exception == null) {
327 throw e;
328 } else {
329 throw EnvHelp.initCause(e, exception);
330 }
331 }
332
333 envcopy = Collections.unmodifiableMap(envcopy);
334
335 return provider.newJMXConnectorServer(serviceURL, envcopy,
336 mbeanServer);
337 }
338 }
|