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 }