1 /* 2 * Copyright (c) 2002, 2015, 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.net.MalformedURLException; 31 import java.util.Collections; 32 import java.util.HashMap; 33 import java.util.Map; 34 import java.util.Iterator; 35 import java.util.ServiceLoader; 36 import java.util.StringTokenizer; 37 import java.security.AccessController; 38 import java.security.PrivilegedAction; 39 40 import com.sun.jmx.remote.util.ClassLogger; 41 import com.sun.jmx.remote.util.EnvHelp; 42 import sun.reflect.misc.ReflectUtil; 43 44 45 /** 46 * <p>Factory to create JMX API connector clients. There 47 * are no instances of this class.</p> 48 * 49 * <p>Connections are usually made using the {@link 50 * #connect(JMXServiceURL) connect} method of this class. More 51 * advanced applications can separate the creation of the connector 52 * client, using {@link #newJMXConnector(JMXServiceURL, Map) 53 * newJMXConnector} and the establishment of the connection itself, using 54 * {@link JMXConnector#connect(Map)}.</p> 55 * 56 * <p>Each client is created by an instance of {@link 57 * JMXConnectorProvider}. This instance is found as follows. Suppose 58 * the given {@link JMXServiceURL} looks like 59 * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>. 60 * Then the factory will attempt to find the appropriate {@link 61 * JMXConnectorProvider} for <code><em>protocol</em></code>. Each 62 * occurrence of the character <code>+</code> or <code>-</code> in 63 * <code><em>protocol</em></code> is replaced by <code>.</code> or 64 * <code>_</code>, respectively.</p> 65 * 66 * <p>A <em>provider package list</em> is searched for as follows:</p> 67 * 68 * <ol> 69 * 70 * <li>If the <code>environment</code> parameter to {@link 71 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the 72 * key <code>jmx.remote.protocol.provider.pkgs</code> then the 73 * associated value is the provider package list. 74 * 75 * <li>Otherwise, if the system property 76 * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value 77 * is the provider package list. 78 * 79 * <li>Otherwise, there is no provider package list. 80 * 81 * </ol> 82 * 83 * <p>The provider package list is a string that is interpreted as a 84 * list of non-empty Java package names separated by vertical bars 85 * (<code>|</code>). If the string is empty, then so is the provider 86 * package list. If the provider package list is not a String, or if 87 * it contains an element that is an empty string, a {@link 88 * JMXProviderException} is thrown.</p> 89 * 90 * <p>If the provider package list exists and is not empty, then for 91 * each element <code><em>pkg</em></code> of the list, the factory 92 * will attempt to load the class 93 * 94 * <blockquote> 95 * <code><em>pkg</em>.<em>protocol</em>.ClientProvider</code> 96 * </blockquote> 97 98 * <p>If the <code>environment</code> parameter to {@link 99 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the 100 * key <code>jmx.remote.protocol.provider.class.loader</code> then the 101 * associated value is the class loader to use to load the provider. 102 * If the associated value is not an instance of {@link 103 * java.lang.ClassLoader}, an {@link 104 * java.lang.IllegalArgumentException} is thrown.</p> 105 * 106 * <p>If the <code>jmx.remote.protocol.provider.class.loader</code> 107 * key is not present in the <code>environment</code> parameter, the 108 * calling thread's context class loader is used.</p> 109 * 110 * <p>If the attempt to load this class produces a {@link 111 * ClassNotFoundException}, the search for a handler continues with 112 * the next element of the list.</p> 113 * 114 * <p>Otherwise, a problem with the provider found is signalled by a 115 * {@link JMXProviderException} whose {@link 116 * JMXProviderException#getCause() <em>cause</em>} indicates the underlying 117 * exception, as follows:</p> 118 * 119 * <ul> 120 * 121 * <li>if the attempt to load the class produces an exception other 122 * than <code>ClassNotFoundException</code>, that is the 123 * <em>cause</em>; 124 * 125 * <li>if {@link Class#newInstance()} for the class produces an 126 * exception, that is the <em>cause</em>. 127 * 128 * </ul> 129 * 130 * <p>If no provider is found by the above steps, including the 131 * default case where there is no provider package list, then the 132 * implementation will use its own provider for 133 * <code><em>protocol</em></code>, or it will throw a 134 * <code>MalformedURLException</code> if there is none. An 135 * implementation may choose to find providers by other means. For 136 * example, it may support the <a 137 * href="{@docRoot}/../technotes/guides/jar/jar.html#Service%20Provider"> 138 * JAR conventions for service providers</a>, where the service 139 * interface is <code>JMXConnectorProvider</code>.</p> 140 * 141 * <p>Every implementation must support the RMI connector protocol with 142 * the default RMI transport, specified with string <code>rmi</code>. 143 * </p> 144 * 145 * <p>Once a provider is found, the result of the 146 * <code>newJMXConnector</code> method is the result of calling {@link 147 * JMXConnectorProvider#newJMXConnector(JMXServiceURL,Map) newJMXConnector} 148 * on the provider.</p> 149 * 150 * <p>The <code>Map</code> parameter passed to the 151 * <code>JMXConnectorProvider</code> is a new read-only 152 * <code>Map</code> that contains all the entries that were in the 153 * <code>environment</code> parameter to {@link 154 * #newJMXConnector(JMXServiceURL,Map) 155 * JMXConnectorFactory.newJMXConnector}, if there was one. 156 * Additionally, if the 157 * <code>jmx.remote.protocol.provider.class.loader</code> key is not 158 * present in the <code>environment</code> parameter, it is added to 159 * the new read-only <code>Map</code>. The associated value is the 160 * calling thread's context class loader.</p> 161 * 162 * @since 1.5 163 */ 164 public class JMXConnectorFactory { 165 166 /** 167 * <p>Name of the attribute that specifies the default class 168 * loader. This class loader is used to deserialize return values and 169 * exceptions from remote <code>MBeanServerConnection</code> 170 * calls. The value associated with this attribute is an instance 171 * of {@link ClassLoader}.</p> 172 */ 173 public static final String DEFAULT_CLASS_LOADER = 174 "jmx.remote.default.class.loader"; 175 176 /** 177 * <p>Name of the attribute that specifies the provider packages 178 * that are consulted when looking for the handler for a protocol. 179 * The value associated with this attribute is a string with 180 * package names separated by vertical bars (<code>|</code>).</p> 181 */ 182 public static final String PROTOCOL_PROVIDER_PACKAGES = 183 "jmx.remote.protocol.provider.pkgs"; 184 185 /** 186 * <p>Name of the attribute that specifies the class 187 * loader for loading protocol providers. 188 * The value associated with this attribute is an instance 189 * of {@link ClassLoader}.</p> 190 */ 191 public static final String PROTOCOL_PROVIDER_CLASS_LOADER = 192 "jmx.remote.protocol.provider.class.loader"; 193 194 private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE = 195 "com.sun.jmx.remote.protocol"; 196 197 private static final ClassLogger logger = 198 new ClassLogger("javax.management.remote.misc", "JMXConnectorFactory"); 199 200 /** There are no instances of this class. */ 201 private JMXConnectorFactory() { 202 } 203 204 /** 205 * <p>Creates a connection to the connector server at the given 206 * address.</p> 207 * 208 * <p>This method is equivalent to {@link 209 * #connect(JMXServiceURL,Map) connect(serviceURL, null)}.</p> 210 * 211 * @param serviceURL the address of the connector server to 212 * connect to. 213 * 214 * @return a <code>JMXConnector</code> whose {@link 215 * JMXConnector#connect connect} method has been called. 216 * 217 * @exception NullPointerException if <code>serviceURL</code> is null. 218 * 219 * @exception IOException if the connector client or the 220 * connection cannot be made because of a communication problem. 221 * 222 * @exception SecurityException if the connection cannot be made 223 * for security reasons. 224 */ 225 public static JMXConnector connect(JMXServiceURL serviceURL) 226 throws IOException { 227 return connect(serviceURL, null); 228 } 229 230 /** 231 * <p>Creates a connection to the connector server at the given 232 * address.</p> 233 * 234 * <p>This method is equivalent to:</p> 235 * 236 * <pre> 237 * JMXConnector conn = JMXConnectorFactory.newJMXConnector(serviceURL, 238 * environment); 239 * conn.connect(environment); 240 * </pre> 241 * 242 * @param serviceURL the address of the connector server to connect to. 243 * 244 * @param environment a set of attributes to determine how the 245 * connection is made. This parameter can be null. Keys in this 246 * map must be Strings. The appropriate type of each associated 247 * value depends on the attribute. The contents of 248 * <code>environment</code> are not changed by this call. 249 * 250 * @return a <code>JMXConnector</code> representing the newly-made 251 * connection. Each successful call to this method produces a 252 * different object. 253 * 254 * @exception NullPointerException if <code>serviceURL</code> is null. 255 * 256 * @exception IOException if the connector client or the 257 * connection cannot be made because of a communication problem. 258 * 259 * @exception SecurityException if the connection cannot be made 260 * for security reasons. 261 */ 262 public static JMXConnector connect(JMXServiceURL serviceURL, 263 Map<String,?> environment) 264 throws IOException { 265 if (serviceURL == null) 266 throw new NullPointerException("Null JMXServiceURL"); 267 JMXConnector conn = newJMXConnector(serviceURL, environment); 268 conn.connect(environment); 269 return conn; 270 } 271 272 private static <K,V> Map<K,V> newHashMap() { 273 return new HashMap<K,V>(); 274 } 275 276 private static <K> Map<K,Object> newHashMap(Map<K,?> map) { 277 return new HashMap<K,Object>(map); 278 } 279 280 /** 281 * <p>Creates a connector client for the connector server at the 282 * given address. The resultant client is not connected until its 283 * {@link JMXConnector#connect(Map) connect} method is called.</p> 284 * 285 * @param serviceURL the address of the connector server to connect to. 286 * 287 * @param environment a set of attributes to determine how the 288 * connection is made. This parameter can be null. Keys in this 289 * map must be Strings. The appropriate type of each associated 290 * value depends on the attribute. The contents of 291 * <code>environment</code> are not changed by this call. 292 * 293 * @return a <code>JMXConnector</code> representing the new 294 * connector client. Each successful call to this method produces 295 * a different object. 296 * 297 * @exception NullPointerException if <code>serviceURL</code> is null. 298 * 299 * @exception IOException if the connector client cannot be made 300 * because of a communication problem. 301 * 302 * @exception MalformedURLException if there is no provider for the 303 * protocol in <code>serviceURL</code>. 304 * 305 * @exception JMXProviderException if there is a provider for the 306 * protocol in <code>serviceURL</code> but it cannot be used for 307 * some reason. 308 */ 309 public static JMXConnector newJMXConnector(JMXServiceURL serviceURL, 310 Map<String,?> environment) 311 throws IOException { 312 313 final Map<String,Object> envcopy; 314 if (environment == null) 315 envcopy = newHashMap(); 316 else { 317 EnvHelp.checkAttributes(environment); 318 envcopy = newHashMap(environment); 319 } 320 321 final ClassLoader loader = resolveClassLoader(envcopy); 322 final Class<JMXConnectorProvider> targetInterface = 323 JMXConnectorProvider.class; 324 final String protocol = serviceURL.getProtocol(); 325 final String providerClassName = "ClientProvider"; 326 final JMXServiceURL providerURL = serviceURL; 327 328 JMXConnectorProvider provider = getProvider(providerURL, envcopy, 329 providerClassName, 330 targetInterface, 331 loader); 332 333 IOException exception = null; 334 if (provider == null) { 335 // Loader is null when context class loader is set to null 336 // and no loader has been provided in map. 337 // com.sun.jmx.remote.util.Service class extracted from j2se 338 // provider search algorithm doesn't handle well null classloader. 339 if (loader != null) { 340 try { 341 JMXConnector connection = 342 getConnectorAsService(loader, providerURL, envcopy); 343 if (connection != null) 344 return connection; 345 } catch (JMXProviderException e) { 346 throw e; 347 } catch (IOException e) { 348 exception = e; 349 } 350 } 351 provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE, 352 JMXConnectorFactory.class.getClassLoader(), 353 providerClassName, targetInterface); 354 } 355 356 if (provider == null) { 357 MalformedURLException e = 358 new MalformedURLException("Unsupported protocol: " + protocol); 359 if (exception == null) { 360 throw e; 361 } else { 362 throw EnvHelp.initCause(e, exception); 363 } 364 } 365 366 final Map<String,Object> fixedenv = 367 Collections.unmodifiableMap(envcopy); 368 369 return provider.newJMXConnector(serviceURL, fixedenv); 370 } 371 372 private static String resolvePkgs(Map<String, ?> env) 373 throws JMXProviderException { 374 375 Object pkgsObject = null; 376 377 if (env != null) 378 pkgsObject = env.get(PROTOCOL_PROVIDER_PACKAGES); 379 380 if (pkgsObject == null) 381 pkgsObject = 382 AccessController.doPrivileged(new PrivilegedAction<String>() { 383 public String run() { 384 return System.getProperty(PROTOCOL_PROVIDER_PACKAGES); 385 } 386 }); 387 388 if (pkgsObject == null) 389 return null; 390 391 if (!(pkgsObject instanceof String)) { 392 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES + 393 " parameter is not a String: " + 394 pkgsObject.getClass().getName(); 395 throw new JMXProviderException(msg); 396 } 397 398 final String pkgs = (String) pkgsObject; 399 if (pkgs.trim().equals("")) 400 return null; 401 402 // pkgs may not contain an empty element 403 if (pkgs.startsWith("|") || pkgs.endsWith("|") || 404 pkgs.indexOf("||") >= 0) { 405 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES + 406 " contains an empty element: " + pkgs; 407 throw new JMXProviderException(msg); 408 } 409 410 return pkgs; 411 } 412 413 static <T> T getProvider(JMXServiceURL serviceURL, 414 final Map<String, Object> environment, 415 String providerClassName, 416 Class<T> targetInterface, 417 final ClassLoader loader) 418 throws IOException { 419 420 final String protocol = serviceURL.getProtocol(); 421 422 final String pkgs = resolvePkgs(environment); 423 424 T instance = null; 425 426 if (pkgs != null) { 427 instance = 428 getProvider(protocol, pkgs, loader, providerClassName, 429 targetInterface); 430 431 if (instance != null) { 432 boolean needsWrap = (loader != instance.getClass().getClassLoader()); 433 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, needsWrap ? wrap(loader) : loader); 434 } 435 } 436 437 return instance; 438 } 439 440 static <T> Iterator<T> getProviderIterator(final Class<T> providerClass, 441 final ClassLoader loader) { 442 ServiceLoader<T> serviceLoader = 443 ServiceLoader.load(providerClass, loader); 444 return serviceLoader.iterator(); 445 } 446 447 private static ClassLoader wrap(final ClassLoader parent) { 448 return parent != null ? AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { 449 @Override 450 public ClassLoader run() { 451 return new ClassLoader(parent) { 452 @Override 453 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 454 ReflectUtil.checkPackageAccess(name); 455 return super.loadClass(name, resolve); 456 } 457 }; 458 } 459 }) : null; 460 } 461 462 private static JMXConnector getConnectorAsService(ClassLoader loader, 463 JMXServiceURL url, 464 Map<String, ?> map) 465 throws IOException { 466 467 Iterator<JMXConnectorProvider> providers = 468 getProviderIterator(JMXConnectorProvider.class, loader); 469 JMXConnector connection; 470 IOException exception = null; 471 while (providers.hasNext()) { 472 JMXConnectorProvider provider = providers.next(); 473 try { 474 connection = provider.newJMXConnector(url, map); 475 return connection; 476 } catch (JMXProviderException e) { 477 throw e; 478 } catch (Exception e) { 479 if (logger.traceOn()) 480 logger.trace("getConnectorAsService", 481 "URL[" + url + 482 "] Service provider exception: " + e); 483 if (!(e instanceof MalformedURLException)) { 484 if (exception == null) { 485 if (e instanceof IOException) { 486 exception = (IOException) e; 487 } else { 488 exception = EnvHelp.initCause( 489 new IOException(e.getMessage()), e); 490 } 491 } 492 } 493 continue; 494 } 495 } 496 if (exception == null) 497 return null; 498 else 499 throw exception; 500 } 501 502 static <T> T getProvider(String protocol, 503 String pkgs, 504 ClassLoader loader, 505 String providerClassName, 506 Class<T> targetInterface) 507 throws IOException { 508 509 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|"); 510 511 while (tokenizer.hasMoreTokens()) { 512 String pkg = tokenizer.nextToken(); 513 String className = (pkg + "." + protocol2package(protocol) + 514 "." + providerClassName); 515 Class<?> providerClass; 516 try { 517 providerClass = Class.forName(className, true, loader); 518 } catch (ClassNotFoundException e) { 519 //Add trace. 520 continue; 521 } 522 523 if (!targetInterface.isAssignableFrom(providerClass)) { 524 final String msg = 525 "Provider class does not implement " + 526 targetInterface.getName() + ": " + 527 providerClass.getName(); 528 throw new JMXProviderException(msg); 529 } 530 531 // We have just proved that this cast is correct 532 Class<? extends T> providerClassT = Util.cast(providerClass); 533 try { 534 @SuppressWarnings("deprecation") 535 T result = providerClassT.newInstance(); 536 return result; 537 } catch (Exception e) { 538 final String msg = 539 "Exception when instantiating provider [" + className + 540 "]"; 541 throw new JMXProviderException(msg, e); 542 } 543 } 544 545 return null; 546 } 547 548 static ClassLoader resolveClassLoader(Map<String, ?> environment) { 549 ClassLoader loader = null; 550 551 if (environment != null) { 552 try { 553 loader = (ClassLoader) 554 environment.get(PROTOCOL_PROVIDER_CLASS_LOADER); 555 } catch (ClassCastException e) { 556 final String msg = 557 "The ClassLoader supplied in the environment map using " + 558 "the " + PROTOCOL_PROVIDER_CLASS_LOADER + 559 " attribute is not an instance of java.lang.ClassLoader"; 560 throw new IllegalArgumentException(msg); 561 } 562 } 563 564 if (loader == null) { 565 loader = Thread.currentThread().getContextClassLoader(); 566 } 567 568 return loader; 569 } 570 571 private static String protocol2package(String protocol) { 572 return protocol.replace('+', '.').replace('-', '_'); 573 } 574 }