1 /*
   2  * Copyright (c) 2002, 2018, 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.io.UncheckedIOException;
  31 import java.net.MalformedURLException;
  32 import java.util.Collections;
  33 import java.util.HashMap;
  34 import java.util.Map;
  35 import java.util.ServiceLoader;
  36 import java.util.ServiceLoader.Provider;
  37 import java.util.StringTokenizer;
  38 import java.util.function.Predicate;
  39 import java.util.stream.Stream;
  40 import java.security.AccessController;
  41 import java.security.PrivilegedAction;
  42 
  43 import com.sun.jmx.remote.util.ClassLogger;
  44 import com.sun.jmx.remote.util.EnvHelp;
  45 import sun.reflect.misc.ReflectUtil;
  46 
  47 
  48 /**
  49  * <p>Factory to create JMX API connector clients.  There
  50  * are no instances of this class.</p>
  51  *
  52  * <p>Connections are usually made using the {@link
  53  * #connect(JMXServiceURL) connect} method of this class.  More
  54  * advanced applications can separate the creation of the connector
  55  * client, using {@link #newJMXConnector(JMXServiceURL, Map)
  56  * newJMXConnector} and the establishment of the connection itself, using
  57  * {@link JMXConnector#connect(Map)}.</p>
  58  *
  59  * <p>Each client is created by an instance of {@link
  60  * JMXConnectorProvider}.  This instance is found as follows.  Suppose
  61  * the given {@link JMXServiceURL} looks like
  62  * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
  63  * Then the factory will attempt to find the appropriate {@link
  64  * JMXConnectorProvider} for <code><em>protocol</em></code>.  Each
  65  * occurrence of the character <code>+</code> or <code>-</code> in
  66  * <code><em>protocol</em></code> is replaced by <code>.</code> or
  67  * <code>_</code>, respectively.</p>
  68  *
  69  * <p>A <em>provider package list</em> is searched for as follows:</p>
  70  *
  71  * <ol>
  72  *
  73  * <li>If the <code>environment</code> parameter to {@link
  74  * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
  75  * key <code>jmx.remote.protocol.provider.pkgs</code> then the
  76  * associated value is the provider package list.
  77  *
  78  * <li>Otherwise, if the system property
  79  * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
  80  * is the provider package list.
  81  *
  82  * <li>Otherwise, there is no provider package list.
  83  *
  84  * </ol>
  85  *
  86  * <p>The provider package list is a string that is interpreted as a
  87  * list of non-empty Java package names separated by vertical bars
  88  * (<code>|</code>).  If the string is empty, then so is the provider
  89  * package list.  If the provider package list is not a String, or if
  90  * it contains an element that is an empty string, a {@link
  91  * JMXProviderException} is thrown.</p>
  92  *
  93  * <p>If the provider package list exists and is not empty, then for
  94  * each element <code><em>pkg</em></code> of the list, the factory
  95  * will attempt to load the class
  96  *
  97  * <blockquote>
  98  * <code><em>pkg</em>.<em>protocol</em>.ClientProvider</code>
  99  * </blockquote>
 100 
 101  * <p>If the <code>environment</code> parameter to {@link
 102  * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
 103  * key <code>jmx.remote.protocol.provider.class.loader</code> then the
 104  * associated value is the class loader to use to load the provider.
 105  * If the associated value is not an instance of {@link
 106  * java.lang.ClassLoader}, an {@link
 107  * java.lang.IllegalArgumentException} is thrown.</p>
 108  *
 109  * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
 110  * key is not present in the <code>environment</code> parameter, the
 111  * calling thread's context class loader is used.</p>
 112  *
 113  * <p>If the attempt to load this class produces a {@link
 114  * ClassNotFoundException}, the search for a handler continues with
 115  * the next element of the list.</p>
 116  *
 117  * <p>Otherwise, a problem with the provider found is signalled by a
 118  * {@link JMXProviderException} whose {@link
 119  * JMXProviderException#getCause() <em>cause</em>} indicates the underlying
 120  * exception, as follows:</p>
 121  *
 122  * <ul>
 123  *
 124  * <li>if the attempt to load the class produces an exception other
 125  * than <code>ClassNotFoundException</code>, that is the
 126  * <em>cause</em>;
 127  *
 128  * <li>if {@link Class#newInstance()} for the class produces an
 129  * exception, that is the <em>cause</em>.
 130  *
 131  * </ul>
 132  *
 133  * <p>If no provider is found by the above steps, including the
 134  * default case where there is no provider package list, then the
 135  * implementation will use its own provider for
 136  * <code><em>protocol</em></code>, or it will throw a
 137  * <code>MalformedURLException</code> if there is none.  An
 138  * implementation may choose to find providers by other means.  For
 139  * example, it may support <a
 140  * href="{@docRoot}/java.base/java/util/ServiceLoader.html#developing-service-providers">service providers</a>,
 141  * where the service interface is <code>JMXConnectorProvider</code>.</p>
 142  *
 143  * <p>Every implementation must support the RMI connector protocol with
 144  * the default RMI transport, specified with string <code>rmi</code>.
 145  * </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             Predicate<Provider<?>> systemProvider =
 338                     JMXConnectorFactory::isSystemProvider;
 339             // Loader is null when context class loader is set to null
 340             // and no loader has been provided in map.
 341             // com.sun.jmx.remote.util.Service class extracted from j2se
 342             // provider search algorithm doesn't handle well null classloader.
 343             JMXConnector connection = null;
 344             if (loader != null) {
 345                 try {
 346                     connection = getConnectorAsService(loader,
 347                                                        providerURL,
 348                                                        envcopy,
 349                                                        systemProvider.negate());
 350                     if (connection != null) return connection;
 351                 } catch (JMXProviderException e) {
 352                     throw e;
 353                 } catch (IOException e) {
 354                     exception = e;
 355                 }
 356             }
 357             connection = getConnectorAsService(
 358                              JMXConnectorFactory.class.getClassLoader(),
 359                              providerURL,
 360                              Collections.unmodifiableMap(envcopy),
 361                              systemProvider);
 362             if (connection != null) return connection;
 363         }
 364 
 365         if (provider == null) {
 366             MalformedURLException e =
 367                 new MalformedURLException("Unsupported protocol: " + protocol);
 368             if (exception == null) {
 369                 throw e;
 370             } else {
 371                 throw EnvHelp.initCause(e, exception);
 372             }
 373         }
 374 
 375         final Map<String,Object> fixedenv =
 376                 Collections.unmodifiableMap(envcopy);
 377 
 378         return provider.newJMXConnector(serviceURL, fixedenv);
 379     }
 380 
 381     private static String resolvePkgs(Map<String, ?> env)
 382             throws JMXProviderException {
 383 
 384         Object pkgsObject = null;
 385 
 386         if (env != null)
 387             pkgsObject = env.get(PROTOCOL_PROVIDER_PACKAGES);
 388 
 389         if (pkgsObject == null)
 390             pkgsObject =
 391                 AccessController.doPrivileged(new PrivilegedAction<String>() {
 392                     public String run() {
 393                         return System.getProperty(PROTOCOL_PROVIDER_PACKAGES);
 394                     }
 395                 });
 396 
 397         if (pkgsObject == null)
 398             return null;
 399 
 400         if (!(pkgsObject instanceof String)) {
 401             final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES +
 402                 " parameter is not a String: " +
 403                 pkgsObject.getClass().getName();
 404             throw new JMXProviderException(msg);
 405         }
 406 
 407         final String pkgs = (String) pkgsObject;
 408         if (pkgs.trim().isEmpty())
 409             return null;
 410 
 411         // pkgs may not contain an empty element
 412         if (pkgs.startsWith("|") || pkgs.endsWith("|") ||
 413             pkgs.indexOf("||") >= 0) {
 414             final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES +
 415                 " contains an empty element: " + pkgs;
 416             throw new JMXProviderException(msg);
 417         }
 418 
 419         return pkgs;
 420     }
 421 
 422     static <T> T getProvider(JMXServiceURL serviceURL,
 423                              final Map<String, Object> environment,
 424                              String providerClassName,
 425                              Class<T> targetInterface,
 426                              final ClassLoader loader)
 427             throws IOException {
 428 
 429         final String protocol = serviceURL.getProtocol();
 430 
 431         final String pkgs = resolvePkgs(environment);
 432 
 433         T instance = null;
 434 
 435         if (pkgs != null) {
 436             instance =
 437                 getProvider(protocol, pkgs, loader, providerClassName,
 438                             targetInterface);
 439 
 440             if (instance != null) {
 441                 boolean needsWrap = (loader != instance.getClass().getClassLoader());
 442                 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, needsWrap ? wrap(loader) : loader);
 443             }
 444         }
 445 
 446         return instance;
 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     /**
 465      * Checks whether the given provider is our system provider for
 466      * the RMI connector.
 467      * If providers for additional protocols are added in the future
 468      * then the name of their modules may need to be added here.
 469      * System providers will be loaded only if no other provider is found.
 470      * @param provider the provider to test.
 471      * @return true if this provider is a default system provider.
 472      */
 473     static boolean isSystemProvider(Provider<?> provider) {
 474         Module providerModule = provider.type().getModule();
 475         return providerModule.isNamed()
 476            && providerModule.getName().equals("java.management.rmi");
 477     }
 478 
 479     /**
 480      * Creates a JMXConnector from the first JMXConnectorProvider service
 481      * supporting the given url that can be loaded from the given loader.
 482      * <p>
 483      * Parses the list of JMXConnectorProvider services that can be loaded
 484      * from the given loader, only retaining those that satisfy the given filter.
 485      * Then for each provider, attempts to create a new JMXConnector.
 486      * The first JMXConnector successfully created is returned.
 487      * <p>
 488      * The filter predicate is usually used to either exclude system providers
 489      * or only retain system providers (see isSystemProvider(...) above).
 490      *
 491      * @param loader The ClassLoader to use when looking up an implementation
 492      *        of the service. If null, then only installed services will be
 493      *        considered.
 494      *
 495      * @param url The JMXServiceURL of the connector for which a provider is
 496      *        requested.
 497      *
 498      * @param filter A filter used to exclude or return provider
 499      *        implementations. Typically the filter will either exclude
 500      *        system services (system default implementations) or only
 501      *        retain those.
 502      *        This can allow to first look for custom implementations (e.g.
 503      *        deployed on the CLASSPATH with META-INF/services) and
 504      *        then only default to system implementations.
 505      *
 506      * @throws IOException if no connector could not be instantiated, and
 507      *         at least one provider threw an exception that wasn't a
 508      *         {@code MalformedURLException} or a {@code JMProviderException}.
 509      *
 510      * @throws JMXProviderException if a provider for the protocol in
 511      *         <code>url</code> was found, but couldn't create the connector
 512      *         some reason.
 513      *
 514      * @return an instance of JMXConnector if a provider was found from
 515      *         which one could be instantiated, {@code null} otherwise.
 516      */
 517     private static JMXConnector getConnectorAsService(ClassLoader loader,
 518                                                       JMXServiceURL url,
 519                                                       Map<String, ?> map,
 520                                                       Predicate<Provider<?>> filter)
 521         throws IOException {
 522 
 523         final ConnectorFactory<JMXConnectorProvider, JMXConnector> factory =
 524                 (p) -> p.newJMXConnector(url, map);
 525         return getConnectorAsService(JMXConnectorProvider.class, loader, url,
 526                                      filter, factory);
 527     }
 528 
 529 
 530     /**
 531      * A factory function that can create a connector from a provider.
 532      * The pair (P,C) will be either one of:
 533      * a. (JMXConnectorProvider, JMXConnector) or
 534      * b. (JMXConnectorServerProvider, JMXConnectorServer)
 535      */
 536     @FunctionalInterface
 537     static interface ConnectorFactory<P,C> {
 538         public C apply(P provider) throws Exception;
 539     }
 540 
 541     /**
 542      * An instance of ProviderFinder is used to traverse a
 543      * {@code Stream<Provider<P>>} and find the first implementation of P
 544      * that supports creating a connector C from the given JMXServiceURL.
 545      * <p>
 546      * The pair (P,C) will be either one of: <br>
 547      * a. (JMXConnectorProvider, JMXConnector) or <br>
 548      * b. (JMXConnectorServerProvider, JMXConnectorServer)
 549      * <p>
 550      * The first connector successfully created while traversing the stream
 551      * is stored in the ProviderFinder instance. After that, the
 552      * ProviderFinder::test method, if called, will always return false, skipping
 553      * the remaining providers.
 554      * <p>
 555      * An instance of ProviderFinder is always expected to be used in conjunction
 556      * with Stream::findFirst, so that the stream traversal is stopped as soon
 557      * as a matching provider is found.
 558      * <p>
 559      * At the end of the stream traversal, the ProviderFinder::get method can be
 560      * used to obtain the connector instance (an instance of C) that was created.
 561      * If no connector could be created, and an exception was encountered while
 562      * traversing the stream and attempting to create the connector, then that
 563      * exception will be thrown by ProviderFinder::get, wrapped, if needed,
 564      * inside an IOException.
 565      * <p>
 566      * If any JMXProviderException is encountered while traversing the stream and
 567      * attempting to create the connector, that exception will be wrapped in an
 568      * UncheckedIOException and thrown immediately within the stream, thus
 569      * interrupting the traversal.
 570      * <p>
 571      * If no matching provider was found (no provider found or attempting
 572      * factory.apply always returned null or threw a MalformedURLException,
 573      * indicating the provider didn't support the protocol asked for by
 574      * the JMXServiceURL), then ProviderFinder::get will simply return null.
 575      */
 576     private static final class ProviderFinder<P,C> implements Predicate<Provider<P>> {
 577 
 578         final ConnectorFactory<P,C> factory;
 579         final JMXServiceURL  url;
 580         private IOException  exception = null;
 581         private C connection = null;
 582 
 583         ProviderFinder(ConnectorFactory<P,C> factory, JMXServiceURL url) {
 584             this.factory = factory;
 585             this.url = url;
 586         }
 587 
 588         /**
 589          * Returns {@code true} for the first provider {@code sp} that can
 590          * be used to obtain an instance of {@code C} from the given
 591          * {@code factory}.
 592          *
 593          * @param sp a candidate provider for instantiating {@code C}.
 594          *
 595          * @throws UncheckedIOException if {@code sp} throws a
 596          *         JMXProviderException. The JMXProviderException is set as the
 597          *         root cause.
 598          *
 599          * @return {@code true} for the first provider {@code sp} for which
 600          *         {@code C} could be instantiated, {@code false} otherwise.
 601          */
 602         public boolean test(Provider<P> sp) {
 603             if (connection == null) {
 604                 P provider = sp.get();
 605                 try {
 606                     connection = factory.apply(provider);
 607                     return connection != null;
 608                 } catch (JMXProviderException e) {
 609                     throw new UncheckedIOException(e);
 610                 } catch (Exception e) {
 611                     if (logger.traceOn())
 612                         logger.trace("getConnectorAsService",
 613                              "URL[" + url +
 614                              "] Service provider exception: " + e);
 615                     if (!(e instanceof MalformedURLException)) {
 616                         if (exception == null) {
 617                             if (e instanceof IOException) {
 618                                 exception = (IOException) e;
 619                             } else {
 620                                 exception = EnvHelp.initCause(
 621                                     new IOException(e.getMessage()), e);
 622                             }
 623                         }
 624                     }
 625                 }
 626             }
 627             return false;
 628         }
 629 
 630         /**
 631          * Returns an instance of {@code C} if a provider was found from
 632          * which {@code C} could be instantiated.
 633          *
 634          * @throws IOException if {@code C} could not be instantiated, and
 635          *         at least one provider threw an exception that wasn't a
 636          *         {@code MalformedURLException} or a {@code JMProviderException}.
 637          *
 638          * @return an instance of {@code C} if a provider was found from
 639          *         which {@code C} could be instantiated, {@code null} otherwise.
 640          */
 641         C get() throws IOException {
 642             if (connection != null) return connection;
 643             else if (exception != null) throw exception;
 644             else return null;
 645         }
 646     }
 647 
 648     /**
 649      * Creates a connector from a provider loaded from the ServiceLoader.
 650      * <p>
 651      * The pair (P,C) will be either one of: <br>
 652      * a. (JMXConnectorProvider, JMXConnector) or <br>
 653      * b. (JMXConnectorServerProvider, JMXConnectorServer)
 654      *
 655      * @param providerClass The service type for which an implementation
 656      *        should be looked up from the {@code ServiceLoader}. This will
 657      *        be either {@code JMXConnectorProvider.class} or
 658      *        {@code JMXConnectorServerProvider.class}
 659      *
 660      * @param loader The ClassLoader to use when looking up an implementation
 661      *        of the service. If null, then only installed services will be
 662      *        considered.
 663      *
 664      * @param url The JMXServiceURL of the connector for which a provider is
 665      *        requested.
 666      *
 667      * @param filter A filter used to exclude or return provider
 668      *        implementations. Typically the filter will either exclude
 669      *        system services (system default implementations) or only
 670      *        retain those.
 671      *        This can allow to first look for custom implementations (e.g.
 672      *        deployed on the CLASSPATH with META-INF/services) and
 673      *        then only default to system implementations.
 674      *
 675      * @param factory A functional factory that can attempt to create an
 676      *        instance of connector {@code C} from a provider {@code P}.
 677      *        Typically, this is a simple wrapper over {@code
 678      *        JMXConnectorProvider::newJMXConnector} or {@code
 679      *        JMXConnectorProviderServer::newJMXConnectorServer}.
 680      *
 681      * @throws IOException if {@code C} could not be instantiated, and
 682      *         at least one provider {@code P} threw an exception that wasn't a
 683      *         {@code MalformedURLException} or a {@code JMProviderException}.
 684      *
 685      * @throws JMXProviderException if a provider {@code P} for the protocol in
 686      *         <code>url</code> was found, but couldn't create the connector
 687      *         {@code C} for some reason.
 688      *
 689      * @return an instance of {@code C} if a provider {@code P} was found from
 690      *         which one could be instantiated, {@code null} otherwise.
 691      */
 692     static <P,C> C getConnectorAsService(Class<P> providerClass,
 693                                          ClassLoader loader,
 694                                          JMXServiceURL url,
 695                                          Predicate<Provider<?>> filter,
 696                                          ConnectorFactory<P,C> factory)
 697         throws IOException {
 698 
 699         // sanity check
 700         if (JMXConnectorProvider.class != providerClass
 701             && JMXConnectorServerProvider.class != providerClass) {
 702             // should never happen
 703             throw new InternalError("Unsupported service interface: "
 704                                     + providerClass.getName());
 705         }
 706 
 707         ServiceLoader<P> serviceLoader = loader == null
 708                 ? ServiceLoader.loadInstalled(providerClass)
 709                 : ServiceLoader.load(providerClass, loader);
 710         Stream<Provider<P>> stream = serviceLoader.stream().filter(filter);
 711         ProviderFinder<P,C> finder = new ProviderFinder<>(factory, url);
 712 
 713         try {
 714             stream.filter(finder).findFirst();
 715             return finder.get();
 716         } catch (UncheckedIOException e) {
 717             if (e.getCause() instanceof JMXProviderException) {
 718                 throw (JMXProviderException) e.getCause();
 719             } else {
 720                 throw e;
 721             }
 722         }
 723     }
 724 
 725     static <T> T getProvider(String protocol,
 726                               String pkgs,
 727                               ClassLoader loader,
 728                               String providerClassName,
 729                               Class<T> targetInterface)
 730             throws IOException {
 731 
 732         StringTokenizer tokenizer = new StringTokenizer(pkgs, "|");
 733 
 734         while (tokenizer.hasMoreTokens()) {
 735             String pkg = tokenizer.nextToken();
 736             String className = (pkg + "." + protocol2package(protocol) +
 737                                 "." + providerClassName);
 738             Class<?> providerClass;
 739             try {
 740                 providerClass = Class.forName(className, true, loader);
 741             } catch (ClassNotFoundException e) {
 742                 //Add trace.
 743                 continue;
 744             }
 745 
 746             if (!targetInterface.isAssignableFrom(providerClass)) {
 747                 final String msg =
 748                     "Provider class does not implement " +
 749                     targetInterface.getName() + ": " +
 750                     providerClass.getName();
 751                 throw new JMXProviderException(msg);
 752             }
 753 
 754             // We have just proved that this cast is correct
 755             Class<? extends T> providerClassT = Util.cast(providerClass);
 756             try {
 757                 @SuppressWarnings("deprecation")
 758                 T result = providerClassT.newInstance();
 759                 return result;
 760             } catch (Exception e) {
 761                 final String msg =
 762                     "Exception when instantiating provider [" + className +
 763                     "]";
 764                 throw new JMXProviderException(msg, e);
 765             }
 766         }
 767 
 768         return null;
 769     }
 770 
 771     static ClassLoader resolveClassLoader(Map<String, ?> environment) {
 772         ClassLoader loader = null;
 773 
 774         if (environment != null) {
 775             try {
 776                 loader = (ClassLoader)
 777                     environment.get(PROTOCOL_PROVIDER_CLASS_LOADER);
 778             } catch (ClassCastException e) {
 779                 final String msg =
 780                     "The ClassLoader supplied in the environment map using " +
 781                     "the " + PROTOCOL_PROVIDER_CLASS_LOADER +
 782                     " attribute is not an instance of java.lang.ClassLoader";
 783                 throw new IllegalArgumentException(msg);
 784             }
 785         }
 786 
 787         if (loader == null) {
 788             loader = Thread.currentThread().getContextClassLoader();
 789         }
 790 
 791         return loader;
 792     }
 793 
 794     private static String protocol2package(String protocol) {
 795         return protocol.replace('+', '.').replace('-', '_');
 796     }
 797 }