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.  */
JMXConnectorFactory()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      */
connect(JMXServiceURL serviceURL)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      */
connect(JMXServiceURL serviceURL, Map<String,?> environment)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 
newHashMap()274     private static <K,V> Map<K,V> newHashMap() {
275         return new HashMap<K,V>();
276     }
277 
newHashMap(Map<K,?> map)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      */
newJMXConnector(JMXServiceURL serviceURL, Map<String,?> environment)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 
resolvePkgs(Map<String, ?> env)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 
getProvider(JMXServiceURL serviceURL, final Map<String, Object> environment, String providerClassName, Class<T> targetInterface, final ClassLoader loader)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 
wrap(final ClassLoader parent)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