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