1/*
2 * Copyright (c) 2002, 2017, 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
26package javax.management.remote;
27
28import com.sun.jmx.mbeanserver.Util;
29import java.io.IOException;
30import java.io.UncheckedIOException;
31import java.net.MalformedURLException;
32import java.util.Collections;
33import java.util.HashMap;
34import java.util.Map;
35import java.util.ServiceLoader;
36import java.util.ServiceLoader.Provider;
37import java.util.StringTokenizer;
38import java.util.function.Predicate;
39import java.util.stream.Stream;
40import java.security.AccessController;
41import java.security.PrivilegedAction;
42
43import com.sun.jmx.remote.util.ClassLogger;
44import com.sun.jmx.remote.util.EnvHelp;
45import 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/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 */
166public 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().equals(""))
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}
798