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
315 envcopy = newHashMap();
316 else {
317 EnvHelp.checkAttributes(environment);
318 envcopy = newHashMap(environment);
319 }
320
321 final ClassLoader loader = resolveClassLoader(envcopy);
322 final Class<JMXConnectorProvider> targetInterface =
323 JMXConnectorProvider.class;
324 final String protocol = serviceURL.getProtocol();
325 final String providerClassName = "ClientProvider";
326 final JMXServiceURL providerURL = serviceURL;
327
328 JMXConnectorProvider provider = getProvider(providerURL, envcopy,
329 providerClassName,
330 targetInterface,
331 loader);
332
333 IOException exception = null;
334 if (provider == null) {
335 // Loader is null when context class loader is set to null
336 // and no loader has been provided in map.
337 // com.sun.jmx.remote.util.Service class extracted from j2se
338 // provider search algorithm doesn't handle well null classloader.
339 if (loader != null) {
340 try {
341 JMXConnector connection =
342 getConnectorAsService(loader, providerURL, envcopy);
343 if (connection != null)
344 return connection;
345 } catch (JMXProviderException e) {
346 throw e;
347 } catch (IOException e) {
348 exception = e;
349 }
350 }
351 provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
352 JMXConnectorFactory.class.getClassLoader(),
353 providerClassName, targetInterface);
354 }
355
356 if (provider == null) {
357 MalformedURLException e =
358 new MalformedURLException("Unsupported protocol: " + protocol);
359 if (exception == null) {
360 throw e;
361 } else {
362 throw EnvHelp.initCause(e, exception);
363 }
364 }
365
366 final Map<String,Object> fixedenv =
367 Collections.unmodifiableMap(envcopy);
368
369 return provider.newJMXConnector(serviceURL, fixedenv);
370 }
371
372 private static String resolvePkgs(Map<String, ?> env)
373 throws JMXProviderException {
420 final String protocol = serviceURL.getProtocol();
421
422 final String pkgs = resolvePkgs(environment);
423
424 T instance = null;
425
426 if (pkgs != null) {
427 instance =
428 getProvider(protocol, pkgs, loader, providerClassName,
429 targetInterface);
430
431 if (instance != null) {
432 boolean needsWrap = (loader != instance.getClass().getClassLoader());
433 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, needsWrap ? wrap(loader) : loader);
434 }
435 }
436
437 return instance;
438 }
439
440 static <T> Iterator<T> getProviderIterator(final Class<T> providerClass,
441 final ClassLoader loader) {
442 ServiceLoader<T> serviceLoader =
443 ServiceLoader.load(providerClass, loader);
444 return serviceLoader.iterator();
445 }
446
447 private static ClassLoader wrap(final ClassLoader parent) {
448 return parent != null ? AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
449 @Override
450 public ClassLoader run() {
451 return new ClassLoader(parent) {
452 @Override
453 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
454 ReflectUtil.checkPackageAccess(name);
455 return super.loadClass(name, resolve);
456 }
457 };
458 }
459 }) : null;
460 }
461
462 private static JMXConnector getConnectorAsService(ClassLoader loader,
463 JMXServiceURL url,
464 Map<String, ?> map)
465 throws IOException {
466
467 Iterator<JMXConnectorProvider> providers =
468 getProviderIterator(JMXConnectorProvider.class, loader);
469 JMXConnector connection;
470 IOException exception = null;
471 while (providers.hasNext()) {
472 JMXConnectorProvider provider = providers.next();
473 try {
474 connection = provider.newJMXConnector(url, map);
475 return connection;
476 } catch (JMXProviderException e) {
477 throw e;
478 } catch (Exception e) {
479 if (logger.traceOn())
480 logger.trace("getConnectorAsService",
481 "URL[" + url +
482 "] Service provider exception: " + e);
483 if (!(e instanceof MalformedURLException)) {
484 if (exception == null) {
485 if (e instanceof IOException) {
486 exception = (IOException) e;
487 } else {
488 exception = EnvHelp.initCause(
489 new IOException(e.getMessage()), e);
490 }
491 }
492 }
493 continue;
494 }
495 }
496 if (exception == null)
497 return null;
498 else
499 throw exception;
500 }
501
502 static <T> T getProvider(String protocol,
503 String pkgs,
504 ClassLoader loader,
505 String providerClassName,
506 Class<T> targetInterface)
507 throws IOException {
508
509 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|");
510
511 while (tokenizer.hasMoreTokens()) {
512 String pkg = tokenizer.nextToken();
513 String className = (pkg + "." + protocol2package(protocol) +
514 "." + providerClassName);
515 Class<?> providerClass;
516 try {
517 providerClass = Class.forName(className, true, loader);
518 } catch (ClassNotFoundException e) {
519 //Add trace.
|
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.io.UncheckedIOException;
31 import java.lang.reflect.Module;
32 import java.net.MalformedURLException;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.ServiceLoader;
37 import java.util.ServiceLoader.Provider;
38 import java.util.StringTokenizer;
39 import java.util.function.Predicate;
40 import java.util.stream.Stream;
41 import java.security.AccessController;
42 import java.security.PrivilegedAction;
43
44 import com.sun.jmx.remote.util.ClassLogger;
45 import com.sun.jmx.remote.util.EnvHelp;
46 import sun.reflect.misc.ReflectUtil;
47
48
49 /**
50 * <p>Factory to create JMX API connector clients. There
51 * are no instances of this class.</p>
52 *
53 * <p>Connections are usually made using the {@link
54 * #connect(JMXServiceURL) connect} method of this class. More
55 * advanced applications can separate the creation of the connector
56 * client, using {@link #newJMXConnector(JMXServiceURL, Map)
57 * newJMXConnector} and the establishment of the connection itself, using
58 * {@link JMXConnector#connect(Map)}.</p>
59 *
60 * <p>Each client is created by an instance of {@link
319 envcopy = newHashMap();
320 else {
321 EnvHelp.checkAttributes(environment);
322 envcopy = newHashMap(environment);
323 }
324
325 final ClassLoader loader = resolveClassLoader(envcopy);
326 final Class<JMXConnectorProvider> targetInterface =
327 JMXConnectorProvider.class;
328 final String protocol = serviceURL.getProtocol();
329 final String providerClassName = "ClientProvider";
330 final JMXServiceURL providerURL = serviceURL;
331
332 JMXConnectorProvider provider = getProvider(providerURL, envcopy,
333 providerClassName,
334 targetInterface,
335 loader);
336
337 IOException exception = null;
338 if (provider == null) {
339 Predicate<Provider<?>> systemProvider =
340 JMXConnectorFactory::isSystemProvider;
341 // Loader is null when context class loader is set to null
342 // and no loader has been provided in map.
343 // com.sun.jmx.remote.util.Service class extracted from j2se
344 // provider search algorithm doesn't handle well null classloader.
345 JMXConnector connection = null;
346 if (loader != null) {
347 try {
348 connection = getConnectorAsService(loader,
349 providerURL,
350 envcopy,
351 systemProvider.negate());
352 if (connection != null) return connection;
353 } catch (JMXProviderException e) {
354 throw e;
355 } catch (IOException e) {
356 exception = e;
357 }
358 }
359 connection = getConnectorAsService(
360 JMXConnectorFactory.class.getClassLoader(),
361 providerURL,
362 Collections.unmodifiableMap(envcopy),
363 systemProvider);
364 if (connection != null) return connection;
365 }
366
367 if (provider == null) {
368 MalformedURLException e =
369 new MalformedURLException("Unsupported protocol: " + protocol);
370 if (exception == null) {
371 throw e;
372 } else {
373 throw EnvHelp.initCause(e, exception);
374 }
375 }
376
377 final Map<String,Object> fixedenv =
378 Collections.unmodifiableMap(envcopy);
379
380 return provider.newJMXConnector(serviceURL, fixedenv);
381 }
382
383 private static String resolvePkgs(Map<String, ?> env)
384 throws JMXProviderException {
431 final String protocol = serviceURL.getProtocol();
432
433 final String pkgs = resolvePkgs(environment);
434
435 T instance = null;
436
437 if (pkgs != null) {
438 instance =
439 getProvider(protocol, pkgs, loader, providerClassName,
440 targetInterface);
441
442 if (instance != null) {
443 boolean needsWrap = (loader != instance.getClass().getClassLoader());
444 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, needsWrap ? wrap(loader) : loader);
445 }
446 }
447
448 return instance;
449 }
450
451 private static ClassLoader wrap(final ClassLoader parent) {
452 return parent != null ? AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
453 @Override
454 public ClassLoader run() {
455 return new ClassLoader(parent) {
456 @Override
457 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
458 ReflectUtil.checkPackageAccess(name);
459 return super.loadClass(name, resolve);
460 }
461 };
462 }
463 }) : null;
464 }
465
466 /**
467 * Checks whether the given provider is our system provider for
468 * the RMI connector.
469 * If providers for additional protocols are added in the future
470 * then the name of their modules may need to be added here.
471 * System providers will be loaded only if no other provider is found.
472 * @param provider the provider to test.
473 * @return true if this provider is a default system provider.
474 */
475 static boolean isSystemProvider(Provider<?> provider) {
476 Module providerModule = provider.type().getModule();
477 return providerModule.isNamed()
478 && providerModule.getName().equals("java.management.rmi");
479 }
480
481 /**
482 * Creates a JMXConnector from the first JMXConnectorProvider service
483 * supporting the given url that can be loaded from the given loader.
484 * <p>
485 * Parses the list of JMXConnectorProvider services that can be loaded
486 * from the given loader, only retaining those that satisfy the given filter.
487 * Then for each provider, attempts to create a new JMXConnector.
488 * The first JMXConnector successfully created is returned.
489 * <p>
490 * The filter predicate is usually used to either exclude system providers
491 * or only retain system providers (see isSystemProvider(...) above).
492 *
493 * @param loader The ClassLoader to use when looking up an implementation
494 * of the service. If null, then only installed services will be
495 * considered.
496 *
497 * @param url The JMXServiceURL of the connector for which a provider is
498 * requested.
499 *
500 * @param filter A filter used to exclude or return provider
501 * implementations. Typically the filter will either exclude
502 * system services (system default implementations) or only
503 * retain those.
504 * This can allow to first look for custom implementations (e.g.
505 * deployed on the CLASSPATH with META-INF/services) and
506 * then only default to system implementations.
507 *
508 * @throws IOException if no connector could not be instantiated, and
509 * at least one provider threw an exception that wasn't a
510 * {@code MalformedURLException} or a {@code JMProviderException}.
511 *
512 * @throws JMXProviderException if a provider for the protocol in
513 * <code>url</code> was found, but couldn't create the connector
514 * some reason.
515 *
516 * @return an instance of JMXConnector if a provider was found from
517 * which one could be instantiated, {@code null} otherwise.
518 */
519 private static JMXConnector getConnectorAsService(ClassLoader loader,
520 JMXServiceURL url,
521 Map<String, ?> map,
522 Predicate<Provider<?>> filter)
523 throws IOException {
524
525 final ConnectorFactory<JMXConnectorProvider, JMXConnector> factory =
526 (p) -> p.newJMXConnector(url, map);
527 return getConnectorAsService(JMXConnectorProvider.class, loader, url,
528 filter, factory);
529 }
530
531
532 /**
533 * A factory function that can create a connector from a provider.
534 * The pair (P,C) will be either one of:
535 * a. (JMXConnectorProvider, JMXConnector) or
536 * b. (JMXConnectorServerProvider, JMXConnectorServer)
537 */
538 @FunctionalInterface
539 static interface ConnectorFactory<P,C> {
540 public C apply(P provider) throws Exception;
541 }
542
543 /**
544 * An instance of ProviderFinder is used to traverse a
545 * {@code Stream<Provider<P>>} and find the first implementation of P
546 * that supports creating a connector C from the given JMXServiceURL.
547 * <p>
548 * The pair (P,C) will be either one of: <br>
549 * a. (JMXConnectorProvider, JMXConnector) or <br>
550 * b. (JMXConnectorServerProvider, JMXConnectorServer)
551 * <p>
552 * The first connector successfully created while traversing the stream
553 * is stored in the ProviderFinder instance. After that, the
554 * ProviderFinder::test method, if called, will always return false, skipping
555 * the remaining providers.
556 * <p>
557 * An instance of ProviderFinder is always expected to be used in conjunction
558 * with Stream::findFirst, so that the stream traversal is stopped as soon
559 * as a matching provider is found.
560 * <p>
561 * At the end of the stream traversal, the ProviderFinder::get method can be
562 * used to obtain the connector instance (an instance of C) that was created.
563 * If no connector could be created, and an exception was encountered while
564 * traversing the stream and attempting to create the connector, then that
565 * exception will be thrown by ProviderFinder::get, wrapped, if needed,
566 * inside an IOException.
567 * <p>
568 * If any JMXProviderException is encountered while traversing the stream and
569 * attempting to create the connector, that exception will be wrapped in an
570 * UncheckedIOException and thrown immediately within the stream, thus
571 * interrupting the traversal.
572 * <p>
573 * If no matching provider was found (no provider found or attempting
574 * factory.apply always returned null or threw a MalformedURLException,
575 * indicating the provider didn't support the protocol asked for by
576 * the JMXServiceURL), then ProviderFinder::get will simply return null.
577 */
578 private static final class ProviderFinder<P,C> implements Predicate<Provider<P>> {
579
580 final ConnectorFactory<P,C> factory;
581 final JMXServiceURL url;
582 private IOException exception = null;
583 private C connection = null;
584
585 ProviderFinder(ConnectorFactory<P,C> factory, JMXServiceURL url) {
586 this.factory = factory;
587 this.url = url;
588 }
589
590 /**
591 * Returns {@code true} for the first provider {@code sp} that can
592 * be used to obtain an instance of {@code C} from the given
593 * {@code factory}.
594 *
595 * @param sp a candidate provider for instantiating {@code C}.
596 *
597 * @throws UncheckedIOException if {@code sp} throws a
598 * JMXProviderException. The JMXProviderException is set as the
599 * root cause.
600 *
601 * @return {@code true} for the first provider {@code sp} for which
602 * {@code C} could be instantiated, {@code false} otherwise.
603 */
604 public boolean test(Provider<P> sp) {
605 if (connection == null) {
606 P provider = sp.get();
607 try {
608 connection = factory.apply(provider);
609 return connection != null;
610 } catch (JMXProviderException e) {
611 throw new UncheckedIOException(e);
612 } catch (Exception e) {
613 if (logger.traceOn())
614 logger.trace("getConnectorAsService",
615 "URL[" + url +
616 "] Service provider exception: " + e);
617 if (!(e instanceof MalformedURLException)) {
618 if (exception == null) {
619 if (e instanceof IOException) {
620 exception = (IOException) e;
621 } else {
622 exception = EnvHelp.initCause(
623 new IOException(e.getMessage()), e);
624 }
625 }
626 }
627 }
628 }
629 return false;
630 }
631
632 /**
633 * Returns an instance of {@code C} if a provider was found from
634 * which {@code C} could be instantiated.
635 *
636 * @throws IOException if {@code C} could not be instantiated, and
637 * at least one provider threw an exception that wasn't a
638 * {@code MalformedURLException} or a {@code JMProviderException}.
639 *
640 * @return an instance of {@code C} if a provider was found from
641 * which {@code C} could be instantiated, {@code null} otherwise.
642 */
643 C get() throws IOException {
644 if (connection != null) return connection;
645 else if (exception != null) throw exception;
646 else return null;
647 }
648 }
649
650 /**
651 * Creates a connector from a provider loaded from the ServiceLoader.
652 * <p>
653 * The pair (P,C) will be either one of: <br>
654 * a. (JMXConnectorProvider, JMXConnector) or <br>
655 * b. (JMXConnectorServerProvider, JMXConnectorServer)
656 *
657 * @param providerClass The service type for which an implementation
658 * should be looked up from the {@code ServiceLoader}. This will
659 * be either {@code JMXConnectorProvider.class} or
660 * {@code JMXConnectorServerProvider.class}
661 *
662 * @param loader The ClassLoader to use when looking up an implementation
663 * of the service. If null, then only installed services will be
664 * considered.
665 *
666 * @param url The JMXServiceURL of the connector for which a provider is
667 * requested.
668 *
669 * @param filter A filter used to exclude or return provider
670 * implementations. Typically the filter will either exclude
671 * system services (system default implementations) or only
672 * retain those.
673 * This can allow to first look for custom implementations (e.g.
674 * deployed on the CLASSPATH with META-INF/services) and
675 * then only default to system implementations.
676 *
677 * @param factory A functional factory that can attempt to create an
678 * instance of connector {@code C} from a provider {@code P}.
679 * Typically, this is a simple wrapper over {@code
680 * JMXConnectorProvider::newJMXConnector} or {@code
681 * JMXConnectorProviderServer::newJMXConnectorServer}.
682 *
683 * @throws IOException if {@code C} could not be instantiated, and
684 * at least one provider {@code P} threw an exception that wasn't a
685 * {@code MalformedURLException} or a {@code JMProviderException}.
686 *
687 * @throws JMXProviderException if a provider {@code P} for the protocol in
688 * <code>url</code> was found, but couldn't create the connector
689 * {@code C} for some reason.
690 *
691 * @return an instance of {@code C} if a provider {@code P} was found from
692 * which one could be instantiated, {@code null} otherwise.
693 */
694 static <P,C> C getConnectorAsService(Class<P> providerClass,
695 ClassLoader loader,
696 JMXServiceURL url,
697 Predicate<Provider<?>> filter,
698 ConnectorFactory<P,C> factory)
699 throws IOException {
700
701 // sanity check
702 if (JMXConnectorProvider.class != providerClass
703 && JMXConnectorServerProvider.class != providerClass) {
704 // should never happen
705 throw new InternalError("Unsupported service interface: "
706 + providerClass.getName());
707 }
708
709 ServiceLoader<P> serviceLoader = loader == null
710 ? ServiceLoader.loadInstalled(providerClass)
711 : ServiceLoader.load(providerClass, loader);
712 Stream<Provider<P>> stream = serviceLoader.stream().filter(filter);
713 ProviderFinder<P,C> finder = new ProviderFinder<>(factory, url);
714
715 try {
716 stream.filter(finder).findFirst();
717 return finder.get();
718 } catch (UncheckedIOException e) {
719 if (e.getCause() instanceof JMXProviderException) {
720 throw (JMXProviderException) e.getCause();
721 } else {
722 throw e;
723 }
724 }
725 }
726
727 static <T> T getProvider(String protocol,
728 String pkgs,
729 ClassLoader loader,
730 String providerClassName,
731 Class<T> targetInterface)
732 throws IOException {
733
734 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|");
735
736 while (tokenizer.hasMoreTokens()) {
737 String pkg = tokenizer.nextToken();
738 String className = (pkg + "." + protocol2package(protocol) +
739 "." + providerClassName);
740 Class<?> providerClass;
741 try {
742 providerClass = Class.forName(className, true, loader);
743 } catch (ClassNotFoundException e) {
744 //Add trace.
|