JMXConnectorFactory.java revision 17482:b3340ac64398
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