1 /*
   2  * Copyright (c) 2003, 2013, 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 
  29 import com.sun.jmx.remote.util.ClassLogger;
  30 import com.sun.jmx.remote.util.EnvHelp;
  31 
  32 import java.io.IOException;
  33 import java.net.MalformedURLException;
  34 import java.util.Collections;
  35 import java.util.HashMap;
  36 import java.util.Iterator;
  37 import java.util.Map;
  38 
  39 import javax.management.MBeanServer;
  40 
  41 /**
  42  * <p>Factory to create JMX API connector servers.  There
  43  * are no instances of this class.</p>
  44  *
  45  * <p>Each connector server is created by an instance of {@link
  46  * JMXConnectorServerProvider}.  This instance is found as follows.  Suppose
  47  * the given {@link JMXServiceURL} looks like
  48  * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
  49  * Then the factory will attempt to find the appropriate {@link
  50  * JMXConnectorServerProvider} for <code><em>protocol</em></code>.  Each
  51  * occurrence of the character <code>+</code> or <code>-</code> in
  52  * <code><em>protocol</em></code> is replaced by <code>.</code> or
  53  * <code>_</code>, respectively.</p>
  54  *
  55  * <p>A <em>provider package list</em> is searched for as follows:</p>
  56  *
  57  * <ol>
  58  *
  59  * <li>If the <code>environment</code> parameter to {@link
  60  * #newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)
  61  * newJMXConnectorServer} contains the key
  62  * <code>jmx.remote.protocol.provider.pkgs</code> then the associated
  63  * value is the provider package list.
  64  *
  65  * <li>Otherwise, if the system property
  66  * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
  67  * is the provider package list.
  68  *
  69  * <li>Otherwise, there is no provider package list.
  70  *
  71  * </ol>
  72  *
  73  * <p>The provider package list is a string that is interpreted as a
  74  * list of non-empty Java package names separated by vertical bars
  75  * (<code>|</code>).  If the string is empty, then so is the provider
  76  * package list.  If the provider package list is not a String, or if
  77  * it contains an element that is an empty string, a {@link
  78  * JMXProviderException} is thrown.</p>
  79  *
  80  * <p>If the provider package list exists and is not empty, then for
  81  * each element <code><em>pkg</em></code> of the list, the factory
  82  * will attempt to load the class
  83  *
  84  * <blockquote>
  85  * <code><em>pkg</em>.<em>protocol</em>.ServerProvider</code>
  86  * </blockquote>
  87 
  88  * <p>If the <code>environment</code> parameter to {@link
  89  * #newJMXConnectorServer(JMXServiceURL, Map, MBeanServer)
  90  * newJMXConnectorServer} contains the key
  91  * <code>jmx.remote.protocol.provider.class.loader</code> then the
  92  * associated value is the class loader to use to load the provider.
  93  * If the associated value is not an instance of {@link
  94  * java.lang.ClassLoader}, an {@link
  95  * java.lang.IllegalArgumentException} is thrown.</p>
  96  *
  97  * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
  98  * key is not present in the <code>environment</code> parameter, the
  99  * calling thread's context class loader is used.</p>
 100  *
 101  * <p>If the attempt to load this class produces a {@link
 102  * ClassNotFoundException}, the search for a handler continues with
 103  * the next element of the list.</p>
 104  *
 105  * <p>Otherwise, a problem with the provider found is signalled by a
 106  * {@link JMXProviderException} whose {@link
 107  * JMXProviderException#getCause() <em>cause</em>} indicates the
 108  * underlying exception, as follows:</p>
 109  *
 110  * <ul>
 111  *
 112  * <li>if the attempt to load the class produces an exception other
 113  * than <code>ClassNotFoundException</code>, that is the
 114  * <em>cause</em>;
 115  *
 116  * <li>if {@link Class#newInstance()} for the class produces an
 117  * exception, that is the <em>cause</em>.
 118  *
 119  * </ul>
 120  *
 121  * <p>If no provider is found by the above steps, including the
 122  * default case where there is no provider package list, then the
 123  * implementation will use its own provider for
 124  * <code><em>protocol</em></code>, or it will throw a
 125  * <code>MalformedURLException</code> if there is none.  An
 126  * implementation may choose to find providers by other means.  For
 127  * example, it may support the <a
 128  * href="{@docRoot}/../technotes/guides/jar/jar.html#Service%20Provider">
 129  * JAR conventions for service providers</a>, where the service
 130  * interface is <code>JMXConnectorServerProvider</code>.</p>
 131  *
 132  * <p>Every implementation must support the RMI connector protocol with
 133  * the default RMI transport, specified with string <code>rmi</code>.
 134  * An implementation may optionally support the RMI connector protocol
 135  * with the RMI/IIOP transport, specified with the string
 136  * <code>iiop</code>.</p>
 137  *
 138  * <p>Once a provider is found, the result of the
 139  * <code>newJMXConnectorServer</code> method is the result of calling
 140  * {@link
 141  * JMXConnectorServerProvider#newJMXConnectorServer(JMXServiceURL,
 142  * Map, MBeanServer) newJMXConnectorServer} on the provider.</p>
 143  *
 144  * <p>The <code>Map</code> parameter passed to the
 145  * <code>JMXConnectorServerProvider</code> is a new read-only
 146  * <code>Map</code> that contains all the entries that were in the
 147  * <code>environment</code> parameter to {@link
 148  * #newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)
 149  * JMXConnectorServerFactory.newJMXConnectorServer}, if there was one.
 150  * Additionally, if the
 151  * <code>jmx.remote.protocol.provider.class.loader</code> key is not
 152  * present in the <code>environment</code> parameter, it is added to
 153  * the new read-only <code>Map</code>. The associated value is the
 154  * calling thread's context class loader.</p>
 155  *
 156  * @since 1.5
 157  */
 158 public class JMXConnectorServerFactory {
 159 
 160     /**
 161      * <p>Name of the attribute that specifies the default class
 162      * loader.  This class loader is used to deserialize objects in
 163      * requests received from the client, possibly after consulting an
 164      * MBean-specific class loader.  The value associated with this
 165      * attribute is an instance of {@link ClassLoader}.</p>
 166      */
 167     public static final String DEFAULT_CLASS_LOADER =
 168         JMXConnectorFactory.DEFAULT_CLASS_LOADER;
 169 
 170     /**
 171      * <p>Name of the attribute that specifies the default class
 172      * loader MBean name.  This class loader is used to deserialize objects in
 173      * requests received from the client, possibly after consulting an
 174      * MBean-specific class loader.  The value associated with this
 175      * attribute is an instance of {@link javax.management.ObjectName
 176      * ObjectName}.</p>
 177      */
 178     public static final String DEFAULT_CLASS_LOADER_NAME =
 179         "jmx.remote.default.class.loader.name";
 180 
 181     /**
 182      * <p>Name of the attribute that specifies the provider packages
 183      * that are consulted when looking for the handler for a protocol.
 184      * The value associated with this attribute is a string with
 185      * package names separated by vertical bars (<code>|</code>).</p>
 186      */
 187     public static final String PROTOCOL_PROVIDER_PACKAGES =
 188         "jmx.remote.protocol.provider.pkgs";
 189 
 190     /**
 191      * <p>Name of the attribute that specifies the class
 192      * loader for loading protocol providers.
 193      * The value associated with this attribute is an instance
 194      * of {@link ClassLoader}.</p>
 195      */
 196     public static final String PROTOCOL_PROVIDER_CLASS_LOADER =
 197         "jmx.remote.protocol.provider.class.loader";
 198 
 199     private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE =
 200         "com.sun.jmx.remote.protocol";
 201 
 202     private static final ClassLogger logger =
 203         new ClassLogger("javax.management.remote.misc","JMXConnectorServerFactory");
 204 
 205     /** There are no instances of this class.  */
 206     private JMXConnectorServerFactory() {
 207     }
 208 
 209     private static JMXConnectorServer
 210         getConnectorServerAsService(ClassLoader loader,
 211                                     JMXServiceURL url,
 212                                     Map<String, ?> map,
 213                                     MBeanServer mbs)
 214         throws IOException {
 215         Iterator<JMXConnectorServerProvider> providers =
 216                 JMXConnectorFactory.
 217                 getProviderIterator(JMXConnectorServerProvider.class, loader);
 218 
 219         IOException exception = null;
 220         while (providers.hasNext()) {
 221             try {
 222                 return providers.next().newJMXConnectorServer(url, map, mbs);
 223             } catch (JMXProviderException e) {
 224                 throw e;
 225             } catch (Exception e) {
 226                 if (logger.traceOn())
 227                     logger.trace("getConnectorAsService",
 228                                  "URL[" + url +
 229                                  "] Service provider exception: " + e);
 230                 if (!(e instanceof MalformedURLException)) {
 231                     if (exception == null) {
 232                         if (e instanceof IOException) {
 233                             exception = (IOException) e;
 234                         } else {
 235                             exception = EnvHelp.initCause(
 236                                 new IOException(e.getMessage()), e);
 237                         }
 238                     }
 239                 }
 240                 continue;
 241             }
 242         }
 243         if (exception == null)
 244             return null;
 245         else
 246             throw exception;
 247     }
 248 
 249     /**
 250      * <p>Creates a connector server at the given address.  The
 251      * resultant server is not started until its {@link
 252      * JMXConnectorServer#start() start} method is called.</p>
 253      *
 254      * @param serviceURL the address of the new connector server.  The
 255      * actual address of the new connector server, as returned by its
 256      * {@link JMXConnectorServer#getAddress() getAddress} method, will
 257      * not necessarily be exactly the same.  For example, it might
 258      * include a port number if the original address did not.
 259      *
 260      * @param environment a set of attributes to control the new
 261      * connector server's behavior.  This parameter can be null.
 262      * Keys in this map must be Strings.  The appropriate type of each
 263      * associated value depends on the attribute.  The contents of
 264      * <code>environment</code> are not changed by this call.
 265      *
 266      * @param mbeanServer the MBean server that this connector server
 267      * is attached to.  Null if this connector server will be attached
 268      * to an MBean server by being registered in it.
 269      *
 270      * @return a <code>JMXConnectorServer</code> representing the new
 271      * connector server.  Each successful call to this method produces
 272      * a different object.
 273      *
 274      * @exception NullPointerException if <code>serviceURL</code> is null.
 275      *
 276      * @exception IOException if the connector server cannot be made
 277      * because of a communication problem.
 278      *
 279      * @exception MalformedURLException if there is no provider for the
 280      * protocol in <code>serviceURL</code>.
 281      *
 282      * @exception JMXProviderException if there is a provider for the
 283      * protocol in <code>serviceURL</code> but it cannot be used for
 284      * some reason.
 285      */
 286     public static JMXConnectorServer
 287         newJMXConnectorServer(JMXServiceURL serviceURL,
 288                               Map<String,?> environment,
 289                               MBeanServer mbeanServer)
 290             throws IOException {
 291         Map<String, Object> envcopy;
 292         if (environment == null)
 293             envcopy = new HashMap<String, Object>();
 294         else {
 295             EnvHelp.checkAttributes(environment);
 296             envcopy = new HashMap<String, Object>(environment);
 297         }
 298 
 299         final Class<JMXConnectorServerProvider> targetInterface =
 300                 JMXConnectorServerProvider.class;
 301         final ClassLoader loader =
 302             JMXConnectorFactory.resolveClassLoader(envcopy);
 303         final String protocol = serviceURL.getProtocol();
 304         final String providerClassName = "ServerProvider";
 305 
 306         JMXConnectorServerProvider provider =
 307             JMXConnectorFactory.getProvider(serviceURL,
 308                                             envcopy,
 309                                             providerClassName,
 310                                             targetInterface,
 311                                             loader);
 312 
 313         IOException exception = null;
 314         if (provider == null) {
 315             // Loader is null when context class loader is set to null
 316             // and no loader has been provided in map.
 317             // com.sun.jmx.remote.util.Service class extracted from j2se
 318             // provider search algorithm doesn't handle well null classloader.
 319             if (loader != null) {
 320                 try {
 321                     JMXConnectorServer connection =
 322                         getConnectorServerAsService(loader,
 323                                                     serviceURL,
 324                                                     envcopy,
 325                                                     mbeanServer);
 326                     if (connection != null)
 327                         return connection;
 328                 } catch (JMXProviderException e) {
 329                     throw e;
 330                 } catch (IOException e) {
 331                     exception = e;
 332                 }
 333             }
 334             provider =
 335                 JMXConnectorFactory.getProvider(
 336                     protocol,
 337                     PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
 338                     JMXConnectorFactory.class.getClassLoader(),
 339                     providerClassName,
 340                     targetInterface);
 341         }
 342 
 343         if (provider == null) {
 344             MalformedURLException e =
 345                 new MalformedURLException("Unsupported protocol: " + protocol);
 346             if (exception == null) {
 347                 throw e;
 348             } else {
 349                 throw EnvHelp.initCause(e, exception);
 350             }
 351         }
 352 
 353         envcopy = Collections.unmodifiableMap(envcopy);
 354 
 355         return provider.newJMXConnectorServer(serviceURL,
 356                                               envcopy,
 357                                               mbeanServer);
 358     }
 359 }