1 /*
2  * Copyright (c) 2003, 2018, 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.io.UncheckedIOException;
34 import java.net.MalformedURLException;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.ServiceLoader.Provider;
39 import java.util.function.Predicate;
40 
41 import javax.management.MBeanServer;
42 import javax.management.remote.JMXConnectorFactory.ConnectorFactory;
43 
44 /**
45  * <p>Factory to create JMX API connector servers.  There
46  * are no instances of this class.</p>
47  *
48  * <p>Each connector server is created by an instance of {@link
49  * JMXConnectorServerProvider}.  This instance is found as follows.  Suppose
50  * the given {@link JMXServiceURL} looks like
51  * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
52  * Then the factory will attempt to find the appropriate {@link
53  * JMXConnectorServerProvider} for <code><em>protocol</em></code>.  Each
54  * occurrence of the character <code>+</code> or <code>-</code> in
55  * <code><em>protocol</em></code> is replaced by <code>.</code> or
56  * <code>_</code>, respectively.</p>
57  *
58  * <p>A <em>provider package list</em> is searched for as follows:</p>
59  *
60  * <ol>
61  *
62  * <li>If the <code>environment</code> parameter to {@link
63  * #newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)
64  * newJMXConnectorServer} contains the key
65  * <code>jmx.remote.protocol.provider.pkgs</code> then the associated
66  * value is the provider package list.
67  *
68  * <li>Otherwise, if the system property
69  * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
70  * is the provider package list.
71  *
72  * <li>Otherwise, there is no provider package list.
73  *
74  * </ol>
75  *
76  * <p>The provider package list is a string that is interpreted as a
77  * list of non-empty Java package names separated by vertical bars
78  * (<code>|</code>).  If the string is empty, then so is the provider
79  * package list.  If the provider package list is not a String, or if
80  * it contains an element that is an empty string, a {@link
81  * JMXProviderException} is thrown.</p>
82  *
83  * <p>If the provider package list exists and is not empty, then for
84  * each element <code><em>pkg</em></code> of the list, the factory
85  * will attempt to load the class
86  *
87  * <blockquote>
88  * <code><em>pkg</em>.<em>protocol</em>.ServerProvider</code>
89  * </blockquote>
90 
91  * <p>If the <code>environment</code> parameter to {@link
92  * #newJMXConnectorServer(JMXServiceURL, Map, MBeanServer)
93  * newJMXConnectorServer} contains the key
94  * <code>jmx.remote.protocol.provider.class.loader</code> then the
95  * associated value is the class loader to use to load the provider.
96  * If the associated value is not an instance of {@link
97  * java.lang.ClassLoader}, an {@link
98  * java.lang.IllegalArgumentException} is thrown.</p>
99  *
100  * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
101  * key is not present in the <code>environment</code> parameter, the
102  * calling thread's context class loader is used.</p>
103  *
104  * <p>If the attempt to load this class produces a {@link
105  * ClassNotFoundException}, the search for a handler continues with
106  * the next element of the list.</p>
107  *
108  * <p>Otherwise, a problem with the provider found is signalled by a
109  * {@link JMXProviderException} whose {@link
110  * JMXProviderException#getCause() <em>cause</em>} indicates the
111  * underlying exception, as follows:</p>
112  *
113  * <ul>
114  *
115  * <li>if the attempt to load the class produces an exception other
116  * than <code>ClassNotFoundException</code>, that is the
117  * <em>cause</em>;
118  *
119  * <li>if {@link Class#newInstance()} for the class produces an
120  * exception, that is the <em>cause</em>.
121  *
122  * </ul>
123  *
124  * <p>If no provider is found by the above steps, including the
125  * default case where there is no provider package list, then the
126  * implementation will use its own provider for
127  * <code><em>protocol</em></code>, or it will throw a
128  * <code>MalformedURLException</code> if there is none.  An
129  * implementation may choose to find providers by other means.  For
130  * example, it may support <a
131  * href="{@docRoot}/java.base/java/util/ServiceLoader.html#developing-service-providers">service providers</a>,
132  * where the service interface is <code>JMXConnectorServerProvider</code>.</p>
133  *
134  * <p>Every implementation must support the RMI connector protocol with
135  * the default RMI transport, specified with string <code>rmi</code>.
136  * </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.  */
JMXConnectorServerFactory()206     private JMXConnectorServerFactory() {
207     }
208 
209     private static JMXConnectorServer
getConnectorServerAsService(ClassLoader loader, JMXServiceURL url, Map<String, ?> map, MBeanServer mbs, Predicate<Provider<?>> filter)210         getConnectorServerAsService(ClassLoader loader, JMXServiceURL url,
211                                     Map<String, ?> map, MBeanServer mbs,
212                                     Predicate<Provider<?>> filter)
213         throws IOException {
214         final ConnectorFactory<JMXConnectorServerProvider,JMXConnectorServer>
215               factory = (p) -> p.newJMXConnectorServer(url, map, mbs);
216         return JMXConnectorFactory.getConnectorAsService(
217                                      JMXConnectorServerProvider.class,
218                                      loader, url, filter, factory);
219     }
220 
221     /**
222      * <p>Creates a connector server at the given address.  The
223      * resultant server is not started until its {@link
224      * JMXConnectorServer#start() start} method is called.</p>
225      *
226      * @param serviceURL the address of the new connector server.  The
227      * actual address of the new connector server, as returned by its
228      * {@link JMXConnectorServer#getAddress() getAddress} method, will
229      * not necessarily be exactly the same.  For example, it might
230      * include a port number if the original address did not.
231      *
232      * @param environment a set of attributes to control the new
233      * connector server's behavior.  This parameter can be null.
234      * Keys in this map must be Strings.  The appropriate type of each
235      * associated value depends on the attribute.  The contents of
236      * <code>environment</code> are not changed by this call.
237      *
238      * @param mbeanServer the MBean server that this connector server
239      * is attached to.  Null if this connector server will be attached
240      * to an MBean server by being registered in it.
241      *
242      * @return a <code>JMXConnectorServer</code> representing the new
243      * connector server.  Each successful call to this method produces
244      * a different object.
245      *
246      * @exception NullPointerException if <code>serviceURL</code> is null.
247      *
248      * @exception IOException if the connector server cannot be made
249      * because of a communication problem.
250      *
251      * @exception MalformedURLException if there is no provider for the
252      * protocol in <code>serviceURL</code>.
253      *
254      * @exception JMXProviderException if there is a provider for the
255      * protocol in <code>serviceURL</code> but it cannot be used for
256      * some reason.
257      */
258     public static JMXConnectorServer
newJMXConnectorServer(JMXServiceURL serviceURL, Map<String,?> environment, MBeanServer mbeanServer)259         newJMXConnectorServer(JMXServiceURL serviceURL,
260                               Map<String,?> environment,
261                               MBeanServer mbeanServer)
262             throws IOException {
263         Map<String, Object> envcopy;
264         if (environment == null)
265             envcopy = new HashMap<String, Object>();
266         else {
267             EnvHelp.checkAttributes(environment);
268             envcopy = new HashMap<String, Object>(environment);
269         }
270 
271         final Class<JMXConnectorServerProvider> targetInterface =
272                 JMXConnectorServerProvider.class;
273         final ClassLoader loader =
274             JMXConnectorFactory.resolveClassLoader(envcopy);
275         final String protocol = serviceURL.getProtocol();
276         final String providerClassName = "ServerProvider";
277 
278         JMXConnectorServerProvider provider =
279             JMXConnectorFactory.getProvider(serviceURL,
280                                             envcopy,
281                                             providerClassName,
282                                             targetInterface,
283                                             loader);
284 
285         IOException exception = null;
286         JMXConnectorServer connection = null;
287         if (provider == null) {
288             Predicate<Provider<?>> systemProvider =
289                     JMXConnectorFactory::isSystemProvider;
290             // Loader is null when context class loader is set to null
291             // and no loader has been provided in map.
292             // com.sun.jmx.remote.util.Service class extracted from j2se
293             // provider search algorithm doesn't handle well null classloader.
294             if (loader != null) {
295                 try {
296                     connection =
297                         getConnectorServerAsService(loader,
298                                                     serviceURL,
299                                                     envcopy,
300                                                     mbeanServer,
301                                                     systemProvider.negate());
302                     if (connection != null)
303                         return connection;
304                 } catch (JMXProviderException e) {
305                     throw e;
306                 } catch (IOException e) {
307                     exception = e;
308                 }
309             }
310             connection = getConnectorServerAsService(
311                             JMXConnectorFactory.class.getClassLoader(),
312                             serviceURL,
313                             Collections.unmodifiableMap(envcopy),
314                             mbeanServer,
315                             systemProvider);
316             if (connection != null) return connection;
317         }
318 
319         if (provider == null) {
320             MalformedURLException e =
321                 new MalformedURLException("Unsupported protocol: " + protocol);
322             if (exception == null) {
323                 throw e;
324             } else {
325                 throw EnvHelp.initCause(e, exception);
326             }
327         }
328 
329         envcopy = Collections.unmodifiableMap(envcopy);
330 
331         return provider.newJMXConnectorServer(serviceURL,
332                                               envcopy,
333                                               mbeanServer);
334     }
335 }
336