001 /*
002 * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.management.remote;
027
028 import com.sun.jmx.mbeanserver.Util;
029 import java.io.IOException;
030 import java.net.MalformedURLException;
031 import java.util.Collections;
032 import java.util.HashMap;
033 import java.util.Map;
034 import java.util.Iterator;
035 import java.util.ServiceLoader;
036 import java.util.StringTokenizer;
037 import java.security.AccessController;
038 import java.security.PrivilegedAction;
039
040 import com.sun.jmx.remote.util.ClassLogger;
041 import com.sun.jmx.remote.util.EnvHelp;
042
043 /**
044 * <p>Factory to create JMX API connector clients. There
045 * are no instances of this class.</p>
046 *
047 * <p>Connections are usually made using the {@link
048 * #connect(JMXServiceURL) connect} method of this class. More
049 * advanced applications can separate the creation of the connector
050 * client, using {@link #newJMXConnector(JMXServiceURL, Map)
051 * newJMXConnector} and the establishment of the connection itself, using
052 * {@link JMXConnector#connect(Map)}.</p>
053 *
054 * <p>Each client is created by an instance of {@link
055 * JMXConnectorProvider}. This instance is found as follows. Suppose
056 * the given {@link JMXServiceURL} looks like
057 * <code>"service:jmx:<em>protocol</em>:<em>remainder</em>"</code>.
058 * Then the factory will attempt to find the appropriate {@link
059 * JMXConnectorProvider} for <code><em>protocol</em></code>. Each
060 * occurrence of the character <code>+</code> or <code>-</code> in
061 * <code><em>protocol</em></code> is replaced by <code>.</code> or
062 * <code>_</code>, respectively.</p>
063 *
064 * <p>A <em>provider package list</em> is searched for as follows:</p>
065 *
066 * <ol>
067 *
068 * <li>If the <code>environment</code> parameter to {@link
069 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
070 * key <code>jmx.remote.protocol.provider.pkgs</code> then the
071 * associated value is the provider package list.
072 *
073 * <li>Otherwise, if the system property
074 * <code>jmx.remote.protocol.provider.pkgs</code> exists, then its value
075 * is the provider package list.
076 *
077 * <li>Otherwise, there is no provider package list.
078 *
079 * </ol>
080 *
081 * <p>The provider package list is a string that is interpreted as a
082 * list of non-empty Java package names separated by vertical bars
083 * (<code>|</code>). If the string is empty, then so is the provider
084 * package list. If the provider package list is not a String, or if
085 * it contains an element that is an empty string, a {@link
086 * JMXProviderException} is thrown.</p>
087 *
088 * <p>If the provider package list exists and is not empty, then for
089 * each element <code><em>pkg</em></code> of the list, the factory
090 * will attempt to load the class
091 *
092 * <blockquote>
093 * <code><em>pkg</em>.<em>protocol</em>.ClientProvider</code>
094 * </blockquote>
095
096 * <p>If the <code>environment</code> parameter to {@link
097 * #newJMXConnector(JMXServiceURL, Map) newJMXConnector} contains the
098 * key <code>jmx.remote.protocol.provider.class.loader</code> then the
099 * associated value is the class loader to use to load the provider.
100 * If the associated value is not an instance of {@link
101 * java.lang.ClassLoader}, an {@link
102 * java.lang.IllegalArgumentException} is thrown.</p>
103 *
104 * <p>If the <code>jmx.remote.protocol.provider.class.loader</code>
105 * key is not present in the <code>environment</code> parameter, the
106 * calling thread's context class loader is used.</p>
107 *
108 * <p>If the attempt to load this class produces a {@link
109 * ClassNotFoundException}, the search for a handler continues with
110 * the next element of the list.</p>
111 *
112 * <p>Otherwise, a problem with the provider found is signalled by a
113 * {@link JMXProviderException} whose {@link
114 * JMXProviderException#getCause() <em>cause</em>} indicates the underlying
115 * exception, as follows:</p>
116 *
117 * <ul>
118 *
119 * <li>if the attempt to load the class produces an exception other
120 * than <code>ClassNotFoundException</code>, that is the
121 * <em>cause</em>;
122 *
123 * <li>if {@link Class#newInstance()} for the class produces an
124 * exception, that is the <em>cause</em>.
125 *
126 * </ul>
127 *
128 * <p>If no provider is found by the above steps, including the
129 * default case where there is no provider package list, then the
130 * implementation will use its own provider for
131 * <code><em>protocol</em></code>, or it will throw a
132 * <code>MalformedURLException</code> if there is none. An
133 * implementation may choose to find providers by other means. For
134 * example, it may support the <a
135 * href="{@docRoot}/../technotes/guides/jar/jar.html#Service Provider">
136 * JAR conventions for service providers</a>, where the service
137 * interface is <code>JMXConnectorProvider</code>.</p>
138 *
139 * <p>Every implementation must support the RMI connector protocols,
140 * specified with the string <code>rmi</code> or
141 * <code>iiop</code>.</p>
142 *
143 * <p>Once a provider is found, the result of the
144 * <code>newJMXConnector</code> method is the result of calling {@link
145 * JMXConnectorProvider#newJMXConnector(JMXServiceURL,Map) newJMXConnector}
146 * on the provider.</p>
147 *
148 * <p>The <code>Map</code> parameter passed to the
149 * <code>JMXConnectorProvider</code> is a new read-only
150 * <code>Map</code> that contains all the entries that were in the
151 * <code>environment</code> parameter to {@link
152 * #newJMXConnector(JMXServiceURL,Map)
153 * JMXConnectorFactory.newJMXConnector}, if there was one.
154 * Additionally, if the
155 * <code>jmx.remote.protocol.provider.class.loader</code> key is not
156 * present in the <code>environment</code> parameter, it is added to
157 * the new read-only <code>Map</code>. The associated value is the
158 * calling thread's context class loader.</p>
159 *
160 * @since 1.5
161 */
162 public class JMXConnectorFactory {
163
164 /**
165 * <p>Name of the attribute that specifies the default class
166 * loader. This class loader is used to deserialize return values and
167 * exceptions from remote <code>MBeanServerConnection</code>
168 * calls. The value associated with this attribute is an instance
169 * of {@link ClassLoader}.</p>
170 */
171 public static final String DEFAULT_CLASS_LOADER = "jmx.remote.default.class.loader";
172
173 /**
174 * <p>Name of the attribute that specifies the provider packages
175 * that are consulted when looking for the handler for a protocol.
176 * The value associated with this attribute is a string with
177 * package names separated by vertical bars (<code>|</code>).</p>
178 */
179 public static final String PROTOCOL_PROVIDER_PACKAGES = "jmx.remote.protocol.provider.pkgs";
180
181 /**
182 * <p>Name of the attribute that specifies the class
183 * loader for loading protocol providers.
184 * The value associated with this attribute is an instance
185 * of {@link ClassLoader}.</p>
186 */
187 public static final String PROTOCOL_PROVIDER_CLASS_LOADER = "jmx.remote.protocol.provider.class.loader";
188
189 private static final String PROTOCOL_PROVIDER_DEFAULT_PACKAGE = "com.sun.jmx.remote.protocol";
190
191 private static final ClassLogger logger = new ClassLogger(
192 "javax.management.remote.misc", "JMXConnectorFactory");
193
194 /** There are no instances of this class. */
195 private JMXConnectorFactory() {
196 }
197
198 /**
199 * <p>Creates a connection to the connector server at the given
200 * address.</p>
201 *
202 * <p>This method is equivalent to {@link
203 * #connect(JMXServiceURL,Map) connect(serviceURL, null)}.</p>
204 *
205 * @param serviceURL the address of the connector server to
206 * connect to.
207 *
208 * @return a <code>JMXConnector</code> whose {@link
209 * JMXConnector#connect connect} method has been called.
210 *
211 * @exception NullPointerException if <code>serviceURL</code> is null.
212 *
213 * @exception IOException if the connector client or the
214 * connection cannot be made because of a communication problem.
215 *
216 * @exception SecurityException if the connection cannot be made
217 * for security reasons.
218 */
219 public static JMXConnector connect(JMXServiceURL serviceURL)
220 throws IOException {
221 return connect(serviceURL, null);
222 }
223
224 /**
225 * <p>Creates a connection to the connector server at the given
226 * address.</p>
227 *
228 * <p>This method is equivalent to:</p>
229 *
230 * <pre>
231 * JMXConnector conn = JMXConnectorFactory.newJMXConnector(serviceURL,
232 * environment);
233 * conn.connect(environment);
234 * </pre>
235 *
236 * @param serviceURL the address of the connector server to connect to.
237 *
238 * @param environment a set of attributes to determine how the
239 * connection is made. This parameter can be null. Keys in this
240 * map must be Strings. The appropriate type of each associated
241 * value depends on the attribute. The contents of
242 * <code>environment</code> are not changed by this call.
243 *
244 * @return a <code>JMXConnector</code> representing the newly-made
245 * connection. Each successful call to this method produces a
246 * different object.
247 *
248 * @exception NullPointerException if <code>serviceURL</code> is null.
249 *
250 * @exception IOException if the connector client or the
251 * connection cannot be made because of a communication problem.
252 *
253 * @exception SecurityException if the connection cannot be made
254 * for security reasons.
255 */
256 public static JMXConnector connect(JMXServiceURL serviceURL,
257 Map<String, ?> environment) throws IOException {
258 if (serviceURL == null)
259 throw new NullPointerException("Null JMXServiceURL");
260 JMXConnector conn = newJMXConnector(serviceURL, environment);
261 conn.connect(environment);
262 return conn;
263 }
264
265 /**
266 * <p>Creates a connector client for the connector server at the
267 * given address. The resultant client is not connected until its
268 * {@link JMXConnector#connect(Map) connect} method is called.</p>
269 *
270 * @param serviceURL the address of the connector server to connect to.
271 *
272 * @param environment a set of attributes to determine how the
273 * connection is made. This parameter can be null. Keys in this
274 * map must be Strings. The appropriate type of each associated
275 * value depends on the attribute. The contents of
276 * <code>environment</code> are not changed by this call.
277 *
278 * @return a <code>JMXConnector</code> representing the new
279 * connector client. Each successful call to this method produces
280 * a different object.
281 *
282 * @exception NullPointerException if <code>serviceURL</code> is null.
283 *
284 * @exception IOException if the connector client cannot be made
285 * because of a communication problem.
286 *
287 * @exception MalformedURLException if there is no provider for the
288 * protocol in <code>serviceURL</code>.
289 *
290 * @exception JMXProviderException if there is a provider for the
291 * protocol in <code>serviceURL</code> but it cannot be used for
292 * some reason.
293 */
294 public static JMXConnector newJMXConnector(
295 JMXServiceURL serviceURL, Map<String, ?> environment)
296 throws IOException {
297 Map<String, Object> envcopy;
298 if (environment == null)
299 envcopy = new HashMap<String, Object>();
300 else {
301 EnvHelp.checkAttributes(environment);
302 envcopy = new HashMap<String, Object>(environment);
303 }
304
305 final ClassLoader loader = resolveClassLoader(envcopy);
306 final Class<JMXConnectorProvider> targetInterface = JMXConnectorProvider.class;
307 final String protocol = serviceURL.getProtocol();
308 final String providerClassName = "ClientProvider";
309
310 JMXConnectorProvider provider = getProvider(serviceURL,
311 envcopy, providerClassName, targetInterface, 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 JMXConnector connection = getConnectorAsService(
322 loader, serviceURL, envcopy);
323 if (connection != null)
324 return connection;
325 } catch (JMXProviderException e) {
326 throw e;
327 } catch (IOException e) {
328 exception = e;
329 }
330 }
331 provider = getProvider(protocol,
332 PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
333 JMXConnectorFactory.class.getClassLoader(),
334 providerClassName, targetInterface);
335 }
336
337 if (provider == null) {
338 MalformedURLException e = new MalformedURLException(
339 "Unsupported protocol: " + protocol);
340 if (exception == null) {
341 throw e;
342 } else {
343 throw EnvHelp.initCause(e, exception);
344 }
345 }
346
347 envcopy = Collections.unmodifiableMap(envcopy);
348
349 return provider.newJMXConnector(serviceURL, envcopy);
350 }
351
352 private static String resolvePkgs(Map env)
353 throws JMXProviderException {
354
355 Object pkgsObject = null;
356
357 if (env != null)
358 pkgsObject = env.get(PROTOCOL_PROVIDER_PACKAGES);
359
360 if (pkgsObject == null)
361 pkgsObject = AccessController
362 .doPrivileged(new PrivilegedAction<Object>() {
363 public Object run() {
364 return System
365 .getProperty(PROTOCOL_PROVIDER_PACKAGES);
366 }
367 });
368
369 if (pkgsObject == null)
370 return null;
371
372 if (!(pkgsObject instanceof String)) {
373 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES
374 + " parameter is not a String: "
375 + pkgsObject.getClass().getName();
376 throw new JMXProviderException(msg);
377 }
378
379 final String pkgs = (String) pkgsObject;
380 if (pkgs.trim().equals(""))
381 return null;
382
383 // pkgs may not contain an empty element
384 if (pkgs.startsWith("|") || pkgs.endsWith("|")
385 || pkgs.indexOf("||") >= 0) {
386 final String msg = "Value of " + PROTOCOL_PROVIDER_PACKAGES
387 + " contains an empty element: " + pkgs;
388 throw new JMXProviderException(msg);
389 }
390
391 return pkgs;
392 }
393
394 static <T> T getProvider(JMXServiceURL serviceURL,
395 Map<String, Object> environment, String providerClassName,
396 Class<T> targetInterface, ClassLoader loader)
397 throws IOException {
398
399 final String protocol = serviceURL.getProtocol();
400
401 final String pkgs = resolvePkgs(environment);
402
403 T instance = null;
404
405 if (pkgs != null) {
406 environment.put(PROTOCOL_PROVIDER_CLASS_LOADER, loader);
407
408 instance = getProvider(protocol, pkgs, loader,
409 providerClassName, targetInterface);
410 }
411
412 return instance;
413 }
414
415 static <T> Iterator<T> getProviderIterator(
416 final Class<T> providerClass, final ClassLoader loader) {
417 ServiceLoader<T> serviceLoader = ServiceLoader.load(
418 providerClass, loader);
419 return serviceLoader.iterator();
420 }
421
422 private static JMXConnector getConnectorAsService(
423 ClassLoader loader, JMXServiceURL url, Map<String, ?> map)
424 throws IOException {
425
426 Iterator<JMXConnectorProvider> providers = getProviderIterator(
427 JMXConnectorProvider.class, loader);
428 JMXConnector connection = null;
429 IOException exception = null;
430 while (providers.hasNext()) {
431 try {
432 connection = providers.next().newJMXConnector(url, map);
433 return connection;
434 } catch (JMXProviderException e) {
435 throw e;
436 } catch (Exception e) {
437 if (logger.traceOn())
438 logger.trace("getConnectorAsService", "URL[" + url
439 + "] Service provider exception: " + e);
440 if (!(e instanceof MalformedURLException)) {
441 if (exception == null) {
442 if (exception instanceof IOException) {
443 exception = (IOException) e;
444 } else {
445 exception = EnvHelp.initCause(
446 new IOException(e.getMessage()), e);
447 }
448 }
449 }
450 continue;
451 }
452 }
453 if (exception == null)
454 return null;
455 else
456 throw exception;
457 }
458
459 static <T> T getProvider(String protocol, String pkgs,
460 ClassLoader loader, String providerClassName,
461 Class<T> targetInterface) throws IOException {
462
463 StringTokenizer tokenizer = new StringTokenizer(pkgs, "|");
464
465 while (tokenizer.hasMoreTokens()) {
466 String pkg = tokenizer.nextToken();
467 String className = (pkg + "." + protocol2package(protocol)
468 + "." + providerClassName);
469 Class<?> providerClass;
470 try {
471 providerClass = Class.forName(className, true, loader);
472 } catch (ClassNotFoundException e) {
473 //Add trace.
474 continue;
475 }
476
477 if (!targetInterface.isAssignableFrom(providerClass)) {
478 final String msg = "Provider class does not implement "
479 + targetInterface.getName() + ": "
480 + providerClass.getName();
481 throw new JMXProviderException(msg);
482 }
483
484 // We have just proved that this cast is correct
485 Class<? extends T> providerClassT = Util
486 .cast(providerClass);
487 try {
488 return providerClassT.newInstance();
489 } catch (Exception e) {
490 final String msg = "Exception when instantiating provider ["
491 + className + "]";
492 throw new JMXProviderException(msg, e);
493 }
494 }
495
496 return null;
497 }
498
499 static ClassLoader resolveClassLoader(Map environment) {
500 ClassLoader loader = null;
501
502 if (environment != null) {
503 try {
504 loader = (ClassLoader) environment
505 .get(PROTOCOL_PROVIDER_CLASS_LOADER);
506 } catch (ClassCastException e) {
507 final String msg = "The ClassLoader supplied in the environment map using "
508 + "the "
509 + PROTOCOL_PROVIDER_CLASS_LOADER
510 + " attribute is not an instance of java.lang.ClassLoader";
511 throw new IllegalArgumentException(msg);
512 }
513 }
514
515 if (loader == null)
516 loader = AccessController
517 .doPrivileged(new PrivilegedAction<ClassLoader>() {
518 public ClassLoader run() {
519 return Thread.currentThread()
520 .getContextClassLoader();
521 }
522 });
523
524 return loader;
525 }
526
527 private static String protocol2package(String protocol) {
528 return protocol.replace('+', '.').replace('-', '_');
529 }
530 }
|