1 /* 2 * Copyright (c) 2002, 2008, 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 Provider"> 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 * An implementation may optionally support the RMI connector protocol 144 * with the RMI/IIOP transport, specified with the string 145 * <code>iiop</code>.</p> 146 * 147 * <p>Once a provider is found, the result of the 148 * <code>newJMXConnector</code> method is the result of calling {@link 149 * JMXConnectorProvider#newJMXConnector(JMXServiceURL,Map) newJMXConnector} 150 * on the provider.</p> 151 * 152 * <p>The <code>Map</code> parameter passed to the 153 * <code>JMXConnectorProvider</code> is a new read-only 154 * <code>Map</code> that contains all the entries that were in the 155 * <code>environment</code> parameter to {@link 156 * #newJMXConnector(JMXServiceURL,Map) 157 * JMXConnectorFactory.newJMXConnector}, if there was one. 158 * Additionally, if the 159 * <code>jmx.remote.protocol.provider.class.loader</code> key is not 160 * present in the <code>environment</code> parameter, it is added to 161 * the new read-only <code>Map</code>. The associated value is the 162 * calling thread's context class loader.</p> 163 * 164 * @since 1.5 165 */ 166 public class JMXConnectorFactory { 167 168 /** 169 * <p>Name of the attribute that specifies the default class 170 * loader. This class loader is used to deserialize return values and 171 * exceptions from remote <code>MBeanServerConnection</code> 172 * calls. The value associated with this attribute is an instance 173 * of {@link ClassLoader}.</p> 174 */ 175 public static final String DEFAULT_CLASS_LOADER = 176 "jmx.remote.default.class.loader"; 177 178 /** 179 * <p>Name of the attribute that specifies the provider packages 180 * that are consulted when looking for the handler for a protocol. 181 * The value associated with this attribute is a string with 182 * package names separated by vertical bars (<code>|</code>).</p> 183 */ 184 public static final String PROTOCOL_PROVIDER_PACKAGES = 185 "jmx.remote.protocol.provider.pkgs"; 186 187 /** 188 * <p>Name of the attribute that specifies the class 189 * loader for loading protocol providers. 190 * The value associated with this attribute is an instance 191 * of {@link ClassLoader}.</p> 192 */ 193 public static final String PROTOCOL_PROVIDER_CLASS_LOADER = 194 "jmx.remote.protocol.provider.class.loader"; 195 196 private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE = 197 "com.sun.jmx.remote.protocol"; 198 199 private static final ClassLogger logger = 200 new ClassLogger("javax.management.remote.misc", "JMXConnectorFactory"); 201 202 /** There are no instances of this class. */ 203 private JMXConnectorFactory() { 204 } 205 206 /** 207 * <p>Creates a connection to the connector server at the given 208 * address.</p> 209 * 210 * <p>This method is equivalent to {@link 211 * #connect(JMXServiceURL,Map) connect(serviceURL, null)}.</p> 212 * 213 * @param serviceURL the address of the connector server to 214 * connect to. 215 * 216 * @return a <code>JMXConnector</code> whose {@link 217 * JMXConnector#connect connect} method has been called. 218 * 219 * @exception NullPointerException if <code>serviceURL</code> is null. 220 * 221 * @exception IOException if the connector client or the 222 * connection cannot be made because of a communication problem. 223 * 224 * @exception SecurityException if the connection cannot be made 225 * for security reasons. 226 */ 227 public static JMXConnector connect(JMXServiceURL serviceURL) 228 throws IOException { 229 return connect(serviceURL, null); 230 } 231 232 /** 233 * <p>Creates a connection to the connector server at the given 234 * address.</p> 235 * 236 * <p>This method is equivalent to:</p> 237 * 238 * <pre> 239 * JMXConnector conn = JMXConnectorFactory.newJMXConnector(serviceURL, 240 * environment); 241 * conn.connect(environment); 242 * </pre> 243 * 244 * @param serviceURL the address of the connector server to connect to. 245 * 246 * @param environment a set of attributes to determine how the 247 * connection is made. This parameter can be null. Keys in this 248 * map must be Strings. The appropriate type of each associated 249 * value depends on the attribute. The contents of 250 * <code>environment</code> are not changed by this call. 251 * 252 * @return a <code>JMXConnector</code> representing the newly-made 253 * connection. Each successful call to this method produces a 254 * different object. 255 * 256 * @exception NullPointerException if <code>serviceURL</code> is null. 257 * 258 * @exception IOException if the connector client or the 259 * connection cannot be made because of a communication problem. 260 * 261 * @exception SecurityException if the connection cannot be made 262 * for security reasons. 263 */ 264 public static JMXConnector connect(JMXServiceURL serviceURL, 265 Map<String,?> environment) 266 throws IOException { 267 if (serviceURL == null) 268 throw new NullPointerException("Null JMXServiceURL"); 269 JMXConnector conn = newJMXConnector(serviceURL, environment); 270 conn.connect(environment); 271 return conn; 272 } 273 274 private static <K,V> Map<K,V> newHashMap() { 275 return new HashMap<K,V>(); 276 } 277 278 private static <K> Map<K,Object> newHashMap(Map<K,?> map) { 279 return new HashMap<K,Object>(map); 280 } 281 282 /** 283 * <p>Creates a connector client for the connector server at the 284 * given address. The resultant client is not connected until its 285 * {@link JMXConnector#connect(Map) connect} method is called.</p> 286 * 287 * @param serviceURL the address of the connector server to connect to. 288 * 289 * @param environment a set of attributes to determine how the 290 * connection is made. This parameter can be null. Keys in this 291 * map must be Strings. The appropriate type of each associated 292 * value depends on the attribute. The contents of 293 * <code>environment</code> are not changed by this call. 294 * 295 * @return a <code>JMXConnector</code> representing the new 296 * connector client. Each successful call to this method produces 297 * a different object. 298 * 299 * @exception NullPointerException if <code>serviceURL</code> is null. 300 * 301 * @exception IOException if the connector client cannot be made 302 * because of a communication problem. 303 * 304 * @exception MalformedURLException if there is no provider for the 305 * protocol in <code>serviceURL</code>. 306 * 307 * @exception JMXProviderException if there is a provider for the 308 * protocol in <code>serviceURL</code> but it cannot be used for 309 * some reason. 310 */ 311 public static JMXConnector newJMXConnector(JMXServiceURL serviceURL, 312 Map<String,?> environment) 313 throws IOException { 314 315 final Map<String,Object> envcopy; 316 if (environment == null) 317 envcopy = newHashMap(); 318 else { 319 EnvHelp.checkAttributes(environment); 320 envcopy = newHashMap(environment); 321 } 322 323 final ClassLoader loader = resolveClassLoader(envcopy); 324 final Class<JMXConnectorProvider> targetInterface = 325 JMXConnectorProvider.class; 326 final String protocol = serviceURL.getProtocol(); 327 final String providerClassName = "ClientProvider"; 328 final JMXServiceURL providerURL = serviceURL; 329 330 JMXConnectorProvider provider = getProvider(providerURL, envcopy, 331 providerClassName, 332 targetInterface, 333 loader); 334 335 IOException exception = null; 336 if (provider == null) { 337 // Loader is null when context class loader is set to null 338 // and no loader has been provided in map. 339 // com.sun.jmx.remote.util.Service class extracted from j2se 340 // provider search algorithm doesn't handle well null classloader. 341 if (loader != null) { 342 try { 343 JMXConnector connection = 344 getConnectorAsService(loader, providerURL, envcopy); 345 if (connection != null) 346 return connection; 347 } catch (JMXProviderException e) { 348 throw e; 349 } catch (IOException e) { 350 exception = e; 351 } 352 } 353 provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE, 354 JMXConnectorFactory.class.getClassLoader(), 355 providerClassName, targetInterface); 356 } 357 358 if (provider == null) { 359 MalformedURLException e = 360 new MalformedURLException("Unsupported protocol: " + protocol); 361 if (exception == null) { 362 throw e; 363 } else { 364 throw EnvHelp.initCause(e, exception); 365 } 366 } 367 368 final Map<String,Object> fixedenv = 369 Collections.unmodifiableMap(envcopy); 370 371 return provider.newJMXConnector(serviceURL, fixedenv); 372 } 373 374 private static String resolvePkgs(Map<String, ?> env) 375 throws JMXProviderException { 376 377 Object pkgsObject = null; 378 379 if (env != null) 380 pkgsObject = env.get(PROTOCOL_PROVIDER_PACKAGES); 381 382 if (pkgsObject == null) 383 pkgsObject = 384 AccessController.doPrivileged(new PrivilegedAction<String>() { 385 public String run() { 386 return System.getProperty(PROTOCOL_PROVIDER_PACKAGES); 387 } 388 }); 389 390 if (pkgsObject == null) 391 return null; 392 393 if (!(pkgsObject instanceof String)) { 394 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES + 395 " parameter is not a String: " + 396 pkgsObject.getClass().getName(); 397 throw new JMXProviderException(msg); 398 } 399 400 final String pkgs = (String) pkgsObject; 401 if (pkgs.trim().equals("")) 402 return null; 403 404 // pkgs may not contain an empty element 405 if (pkgs.startsWith("|") || pkgs.endsWith("|") || 406 pkgs.indexOf("||") >= 0) { 407 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES + 408 " contains an empty element: " + pkgs; 409 throw new JMXProviderException(msg); 410 } 411 412 return pkgs; 413 } 414 415 static <T> T getProvider(JMXServiceURL serviceURL, 416 final Map<String, Object> environment, 417 String providerClassName, 418 Class<T> targetInterface, 419 final ClassLoader loader) 420 throws IOException { 421 422 final String protocol = serviceURL.getProtocol(); 423 424 final String pkgs = resolvePkgs(environment); 425 426 T instance = null; 427 428 if (pkgs != null) { 429 instance = 430 getProvider(protocol, pkgs, loader, providerClassName, 431 targetInterface); 432 433 if (instance != null) { 434 boolean needsWrap = (loader != instance.getClass().getClassLoader()); 435 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, needsWrap ? wrap(loader) : loader); 436 } 437 } 438 439 return instance; 440 } 441 442 static <T> Iterator<T> getProviderIterator(final Class<T> providerClass, 443 final ClassLoader loader) { 444 ServiceLoader<T> serviceLoader = 445 ServiceLoader.load(providerClass, loader); 446 return serviceLoader.iterator(); 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 private static JMXConnector getConnectorAsService(ClassLoader loader, 465 JMXServiceURL url, 466 Map<String, ?> map) 467 throws IOException { 468 469 Iterator<JMXConnectorProvider> providers = 470 getProviderIterator(JMXConnectorProvider.class, loader); 471 JMXConnector connection; 472 IOException exception = null; 473 while (providers.hasNext()) { 474 JMXConnectorProvider provider = providers.next(); 475 try { 476 connection = provider.newJMXConnector(url, map); 477 return connection; 478 } catch (JMXProviderException e) { 479 throw e; 480 } catch (Exception e) { 481 if (logger.traceOn()) 482 logger.trace("getConnectorAsService", 483 "URL[" + url + 484 "] Service provider exception: " + e); 485 if (!(e instanceof MalformedURLException)) { 486 if (exception == null) { 487 if (e instanceof IOException) { 488 exception = (IOException) e; 489 } else { 490 exception = EnvHelp.initCause( 491 new IOException(e.getMessage()), e); 492 } 493 } 494 } 495 continue; 496 } 497 } 498 if (exception == null) 499 return null; 500 else 501 throw exception; 502 } 503 504 static <T> T getProvider(String protocol, 505 String pkgs, 506 ClassLoader loader, 507 String providerClassName, 508 Class<T> targetInterface) 509 throws IOException { 510 511 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|"); 512 513 while (tokenizer.hasMoreTokens()) { 514 String pkg = tokenizer.nextToken(); 515 String className = (pkg + "." + protocol2package(protocol) + 516 "." + providerClassName); 517 Class<?> providerClass; 518 try { 519 providerClass = Class.forName(className, true, loader); 520 } catch (ClassNotFoundException e) { 521 //Add trace. 522 continue; 523 } 524 525 if (!targetInterface.isAssignableFrom(providerClass)) { 526 final String msg = 527 "Provider class does not implement " + 528 targetInterface.getName() + ": " + 529 providerClass.getName(); 530 throw new JMXProviderException(msg); 531 } 532 533 // We have just proved that this cast is correct 534 Class<? extends T> providerClassT = Util.cast(providerClass); 535 try { 536 return providerClassT.newInstance(); 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 }