blob: 7b57c8621a42e086cf87d316305ae8e6008b6aa2 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.management.remote;
27
28import com.sun.jmx.mbeanserver.Util;
29import java.io.IOException;
30import java.net.MalformedURLException;
31import java.util.Collections;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.Iterator;
35import java.util.ServiceLoader;
36import java.util.StringTokenizer;
37import java.security.AccessController;
38import java.security.PrivilegedAction;
39
40import com.sun.jmx.remote.util.ClassLogger;
41import com.sun.jmx.remote.util.EnvHelp;
42
43
44/**
45 * <p>Factory to create JMX API connector clients. There
46 * are no instances of this class.</p>
47 *
48 * <p>Connections are usually made using the {@link
49 * #connect(JMXServiceURL) connect} method of this class. More
50 * advanced applications can separate the creation of the connector
51 * client, using {@link #newJMXConnector(JMXServiceURL, Map)
52 * newJMXConnector} and the establishment of the connection itself, using
53 * {@link JMXConnector#connect(Map)}.</p>
54 *
55 * <p>Each client is created by an instance of {@link
56 * JMXConnectorProvider}. This instance is found as follows. Suppose
57 * the given {@link JMXServiceURL} looks like
58 * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
59 * Then the factory will attempt to find the appropriate {@link
60 * JMXConnectorProvider} for <code><em>protocol</em></code>. Each
61 * occurrence of the character <code>+</code> or <code>-</code> in
62 * <code><em>protocol</em></code> is replaced by <code>.</code> or
63 * <code>_</code>, respectively.</p>
64 *
65 * <p>A <em>provider package list</em> is searched for as follows:</p>
66 *
67 * <ol>
68 *
69 * <li>If the <code>environment</code> parameter to {@link
70 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
71 * key <code>jmx.remote.protocol.provider.pkgs</code> then the
72 * associated value is the provider package list.
73 *
74 * <li>Otherwise, if the system property
75 * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
76 * is the provider package list.
77 *
78 * <li>Otherwise, there is no provider package list.
79 *
80 * </ol>
81 *
82 * <p>The provider package list is a string that is interpreted as a
83 * list of non-empty Java package names separated by vertical bars
84 * (<code>|</code>). If the string is empty, then so is the provider
85 * package list. If the provider package list is not a String, or if
86 * it contains an element that is an empty string, a {@link
87 * JMXProviderException} is thrown.</p>
88 *
89 * <p>If the provider package list exists and is not empty, then for
90 * each element <code><em>pkg</em></code> of the list, the factory
91 * will attempt to load the class
92 *
93 * <blockquote>
94 * <code><em>pkg</em>.<em>protocol</em>.ClientProvider</code>
95 * </blockquote>
96
97 * <p>If the <code>environment</code> parameter to {@link
98 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
99 * key <code>jmx.remote.protocol.provider.class.loader</code> then the
100 * associated value is the class loader to use to load the provider.
101 * If the associated value is not an instance of {@link
102 * java.lang.ClassLoader}, an {@link
103 * java.lang.IllegalArgumentException} is thrown.</p>
104 *
105 * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
106 * key is not present in the <code>environment</code> parameter, the
107 * calling thread's context class loader is used.</p>
108 *
109 * <p>If the attempt to load this class produces a {@link
110 * ClassNotFoundException}, the search for a handler continues with
111 * the next element of the list.</p>
112 *
113 * <p>Otherwise, a problem with the provider found is signalled by a
114 * {@link JMXProviderException} whose {@link
115 * JMXProviderException#getCause() <em>cause</em>} indicates the underlying
116 * exception, as follows:</p>
117 *
118 * <ul>
119 *
120 * <li>if the attempt to load the class produces an exception other
121 * than <code>ClassNotFoundException</code>, that is the
122 * <em>cause</em>;
123 *
124 * <li>if {@link Class#newInstance()} for the class produces an
125 * exception, that is the <em>cause</em>.
126 *
127 * </ul>
128 *
129 * <p>If no provider is found by the above steps, including the
130 * default case where there is no provider package list, then the
131 * implementation will use its own provider for
132 * <code><em>protocol</em></code>, or it will throw a
133 * <code>MalformedURLException</code> if there is none. An
134 * implementation may choose to find providers by other means. For
135 * example, it may support the <a
136 * href="{@docRoot}/../technotes/guides/jar/jar.html#Service Provider">
137 * JAR conventions for service providers</a>, where the service
138 * interface is <code>JMXConnectorProvider</code>.</p>
139 *
140 * <p>Every implementation must support the RMI connector protocols,
141 * specified with the string <code>rmi</code> or
142 * <code>iiop</code>.</p>
143 *
144 * <p>Once a provider is found, the result of the
145 * <code>newJMXConnector</code> method is the result of calling {@link
146 * JMXConnectorProvider#newJMXConnector(JMXServiceURL,Map) newJMXConnector}
147 * on the provider.</p>
148 *
149 * <p>The <code>Map</code> parameter passed to the
150 * <code>JMXConnectorProvider</code> is a new read-only
151 * <code>Map</code> that contains all the entries that were in the
152 * <code>environment</code> parameter to {@link
153 * #newJMXConnector(JMXServiceURL,Map)
154 * JMXConnectorFactory.newJMXConnector}, if there was one.
155 * Additionally, if the
156 * <code>jmx.remote.protocol.provider.class.loader</code> key is not
157 * present in the <code>environment</code> parameter, it is added to
158 * the new read-only <code>Map</code>. The associated value is the
159 * calling thread's context class loader.</p>
160 *
161 * @since 1.5
162 */
163public class JMXConnectorFactory {
164
165 /**
166 * <p>Name of the attribute that specifies the default class
167 * loader. This class loader is used to deserialize return values and
168 * exceptions from remote <code>MBeanServerConnection</code>
169 * calls. The value associated with this attribute is an instance
170 * of {@link ClassLoader}.</p>
171 */
172 public static final String DEFAULT_CLASS_LOADER =
173 "jmx.remote.default.class.loader";
174
175 /**
176 * <p>Name of the attribute that specifies the provider packages
177 * that are consulted when looking for the handler for a protocol.
178 * The value associated with this attribute is a string with
179 * package names separated by vertical bars (<code>|</code>).</p>
180 */
181 public static final String PROTOCOL_PROVIDER_PACKAGES =
182 "jmx.remote.protocol.provider.pkgs";
183
184 /**
185 * <p>Name of the attribute that specifies the class
186 * loader for loading protocol providers.
187 * The value associated with this attribute is an instance
188 * of {@link ClassLoader}.</p>
189 */
190 public static final String PROTOCOL_PROVIDER_CLASS_LOADER =
191 "jmx.remote.protocol.provider.class.loader";
192
193 private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE =
194 "com.sun.jmx.remote.protocol";
195
196 private static final ClassLogger logger =
197 new ClassLogger("javax.management.remote.misc", "JMXConnectorFactory");
198
199 /** There are no instances of this class. */
200 private JMXConnectorFactory() {
201 }
202
203 /**
204 * <p>Creates a connection to the connector server at the given
205 * address.</p>
206 *
207 * <p>This method is equivalent to {@link
208 * #connect(JMXServiceURL,Map) connect(serviceURL, null)}.</p>
209 *
210 * @param serviceURL the address of the connector server to
211 * connect to.
212 *
213 * @return a <code>JMXConnector</code> whose {@link
214 * JMXConnector#connect connect} method has been called.
215 *
216 * @exception NullPointerException if <code>serviceURL</code> is null.
217 *
218 * @exception IOException if the connector client or the
219 * connection cannot be made because of a communication problem.
220 *
221 * @exception SecurityException if the connection cannot be made
222 * for security reasons.
223 */
224 public static JMXConnector connect(JMXServiceURL serviceURL)
225 throws IOException {
226 return connect(serviceURL, null);
227 }
228
229 /**
230 * <p>Creates a connection to the connector server at the given
231 * address.</p>
232 *
233 * <p>This method is equivalent to:</p>
234 *
235 * <pre>
236 * JMXConnector conn = JMXConnectorFactory.newJMXConnector(serviceURL,
237 * environment);
238 * conn.connect(environment);
239 * </pre>
240 *
241 * @param serviceURL the address of the connector server to connect to.
242 *
243 * @param environment a set of attributes to determine how the
244 * connection is made. This parameter can be null. Keys in this
245 * map must be Strings. The appropriate type of each associated
246 * value depends on the attribute. The contents of
247 * <code>environment</code> are not changed by this call.
248 *
249 * @return a <code>JMXConnector</code> representing the newly-made
250 * connection. Each successful call to this method produces a
251 * different object.
252 *
253 * @exception NullPointerException if <code>serviceURL</code> is null.
254 *
255 * @exception IOException if the connector client or the
256 * connection cannot be made because of a communication problem.
257 *
258 * @exception SecurityException if the connection cannot be made
259 * for security reasons.
260 */
261 public static JMXConnector connect(JMXServiceURL serviceURL,
262 Map<String,?> environment)
263 throws IOException {
264 if (serviceURL == null)
265 throw new NullPointerException("Null JMXServiceURL");
266 JMXConnector conn = newJMXConnector(serviceURL, environment);
267 conn.connect(environment);
268 return conn;
269 }
270
271 /**
272 * <p>Creates a connector client for the connector server at the
273 * given address. The resultant client is not connected until its
274 * {@link JMXConnector#connect(Map) connect} method is called.</p>
275 *
276 * @param serviceURL the address of the connector server to connect to.
277 *
278 * @param environment a set of attributes to determine how the
279 * connection is made. This parameter can be null. Keys in this
280 * map must be Strings. The appropriate type of each associated
281 * value depends on the attribute. The contents of
282 * <code>environment</code> are not changed by this call.
283 *
284 * @return a <code>JMXConnector</code> representing the new
285 * connector client. Each successful call to this method produces
286 * a different object.
287 *
288 * @exception NullPointerException if <code>serviceURL</code> is null.
289 *
290 * @exception IOException if the connector client cannot be made
291 * because of a communication problem.
292 *
293 * @exception MalformedURLException if there is no provider for the
294 * protocol in <code>serviceURL</code>.
295 *
296 * @exception JMXProviderException if there is a provider for the
297 * protocol in <code>serviceURL</code> but it cannot be used for
298 * some reason.
299 */
300 public static JMXConnector newJMXConnector(JMXServiceURL serviceURL,
301 Map<String,?> environment)
302 throws IOException {
303 Map<String, Object> envcopy;
304 if (environment == null)
305 envcopy = new HashMap<String, Object>();
306 else {
307 EnvHelp.checkAttributes(environment);
308 envcopy = new HashMap<String, Object>(environment);
309 }
310
311 final ClassLoader loader = resolveClassLoader(envcopy);
312 final Class<JMXConnectorProvider> targetInterface = JMXConnectorProvider.class;
313 final String protocol = serviceURL.getProtocol();
314 final String providerClassName = "ClientProvider";
315
316 JMXConnectorProvider provider =
317 getProvider(serviceURL, envcopy, providerClassName,
318 targetInterface, loader);
319
320 IOException exception = null;
321 if (provider == null) {
322 // Loader is null when context class loader is set to null
323 // and no loader has been provided in map.
324 // com.sun.jmx.remote.util.Service class extracted from j2se
325 // provider search algorithm doesn't handle well null classloader.
326 if (loader != null) {
327 try {
328 JMXConnector connection =
329 getConnectorAsService(loader, serviceURL, envcopy);
330 if (connection != null)
331 return connection;
332 } catch (JMXProviderException e) {
333 throw e;
334 } catch (IOException e) {
335 exception = e;
336 }
337 }
338 provider =
339 getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
340 JMXConnectorFactory.class.getClassLoader(),
341 providerClassName, targetInterface);
342 }
343
344 if (provider == null) {
345 MalformedURLException e =
346 new MalformedURLException("Unsupported protocol: " + protocol);
347 if (exception == null) {
348 throw e;
349 } else {
350 throw EnvHelp.initCause(e, exception);
351 }
352 }
353
354 envcopy = Collections.unmodifiableMap(envcopy);
355
356 return provider.newJMXConnector(serviceURL, envcopy);
357 }
358
359 private static String resolvePkgs(Map env) throws JMXProviderException {
360
361 Object pkgsObject = null;
362
363 if (env != null)
364 pkgsObject = env.get(PROTOCOL_PROVIDER_PACKAGES);
365
366 if (pkgsObject == null)
367 pkgsObject =
368 AccessController.doPrivileged(new PrivilegedAction<Object>() {
369 public Object run() {
370 return System.getProperty(PROTOCOL_PROVIDER_PACKAGES);
371 }
372 });
373
374 if (pkgsObject == null)
375 return null;
376
377 if (!(pkgsObject instanceof String)) {
378 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES +
379 " parameter is not a String: " +
380 pkgsObject.getClass().getName();
381 throw new JMXProviderException(msg);
382 }
383
384 final String pkgs = (String) pkgsObject;
385 if (pkgs.trim().equals(""))
386 return null;
387
388 // pkgs may not contain an empty element
389 if (pkgs.startsWith("|") || pkgs.endsWith("|") ||
390 pkgs.indexOf("||") >= 0) {
391 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES +
392 " contains an empty element: " + pkgs;
393 throw new JMXProviderException(msg);
394 }
395
396 return pkgs;
397 }
398
399 static <T> T getProvider(JMXServiceURL serviceURL,
400 Map<String, Object> environment,
401 String providerClassName,
402 Class<T> targetInterface,
403 ClassLoader loader)
404 throws IOException {
405
406 final String protocol = serviceURL.getProtocol();
407
408 final String pkgs = resolvePkgs(environment);
409
410 T instance = null;
411
412 if (pkgs != null) {
413 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, loader);
414
415 instance =
416 getProvider(protocol, pkgs, loader, providerClassName,
417 targetInterface);
418 }
419
420 return instance;
421 }
422
423 static <T> Iterator<T> getProviderIterator(final Class<T> providerClass,
424 final ClassLoader loader) {
425 ServiceLoader<T> serviceLoader =
426 ServiceLoader.load(providerClass,
427 loader);
428 return serviceLoader.iterator();
429 }
430
431 private static JMXConnector getConnectorAsService(ClassLoader loader,
432 JMXServiceURL url,
433 Map<String, ?> map)
434 throws IOException {
435
436 Iterator<JMXConnectorProvider> providers =
437 getProviderIterator(JMXConnectorProvider.class, loader);
438 JMXConnector connection = null;
439 IOException exception = null;
440 while(providers.hasNext()) {
441 try {
442 connection = providers.next().newJMXConnector(url, map);
443 return connection;
444 } catch (JMXProviderException e) {
445 throw e;
446 } catch (Exception e) {
447 if (logger.traceOn())
448 logger.trace("getConnectorAsService",
449 "URL[" + url +
450 "] Service provider exception: " + e);
451 if (!(e instanceof MalformedURLException)) {
452 if (exception == null) {
453 if (exception instanceof IOException) {
454 exception = (IOException) e;
455 } else {
456 exception = EnvHelp.initCause(
457 new IOException(e.getMessage()), e);
458 }
459 }
460 }
461 continue;
462 }
463 }
464 if (exception == null)
465 return null;
466 else
467 throw exception;
468 }
469
470 static <T> T getProvider(String protocol,
471 String pkgs,
472 ClassLoader loader,
473 String providerClassName,
474 Class<T> targetInterface)
475 throws IOException {
476
477 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|");
478
479 while (tokenizer.hasMoreTokens()) {
480 String pkg = tokenizer.nextToken();
481 String className = (pkg + "." + protocol2package(protocol) +
482 "." + providerClassName);
483 Class<?> providerClass;
484 try {
485 providerClass = Class.forName(className, true, loader);
486 } catch (ClassNotFoundException e) {
487 //Add trace.
488 continue;
489 }
490
491 if (!targetInterface.isAssignableFrom(providerClass)) {
492 final String msg =
493 "Provider class does not implement " +
494 targetInterface.getName() + ": " +
495 providerClass.getName();
496 throw new JMXProviderException(msg);
497 }
498
499 // We have just proved that this cast is correct
500 Class<? extends T> providerClassT = Util.cast(providerClass);
501 try {
502 return providerClassT.newInstance();
503 } catch (Exception e) {
504 final String msg =
505 "Exception when instantiating provider [" + className +
506 "]";
507 throw new JMXProviderException(msg, e);
508 }
509 }
510
511 return null;
512 }
513
514 static ClassLoader resolveClassLoader(Map environment) {
515 ClassLoader loader = null;
516
517 if (environment != null) {
518 try {
519 loader = (ClassLoader)
520 environment.get(PROTOCOL_PROVIDER_CLASS_LOADER);
521 } catch (ClassCastException e) {
522 final String msg =
523 "The ClassLoader supplied in the environment map using " +
524 "the " + PROTOCOL_PROVIDER_CLASS_LOADER +
525 " attribute is not an instance of java.lang.ClassLoader";
526 throw new IllegalArgumentException(msg);
527 }
528 }
529
530 if (loader == null)
531 loader =
532 AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
533 public ClassLoader run() {
534 return
535 Thread.currentThread().getContextClassLoader();
536 }
537 });
538
539 return loader;
540 }
541
542 private static String protocol2package(String protocol) {
543 return protocol.replace('+', '.').replace('-', '_');
544 }
545}