View Javadoc
1   
2   /*
3    * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
4    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5    *
6    * This code is free software; you can redistribute it and/or modify it
7    * under the terms of the GNU General Public License version 2 only, as
8    * published by the Free Software Foundation.  Oracle designates this
9    * particular file as subject to the "Classpath" exception as provided
10   * by Oracle in the LICENSE file that accompanied this code.
11   *
12   * This code is distributed in the hope that it will be useful, but WITHOUT
13   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15   * version 2 for more details (a copy is included in the LICENSE file that
16   * accompanied this code).
17   *
18   * You should have received a copy of the GNU General Public License version
19   * 2 along with this work; if not, write to the Free Software Foundation,
20   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21   *
22   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23   * or visit www.oracle.com if you need additional information or have any
24   * questions.
25   */
26  
27  package com.sun.jmx.remote.util;
28  
29  import java.io.IOException;
30  import java.io.ObjectOutputStream;
31  import java.io.OutputStream;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.Hashtable;
35  import java.util.Iterator;
36  import java.util.Map;
37  import java.util.SortedMap;
38  import java.util.SortedSet;
39  import java.util.StringTokenizer;
40  import java.util.TreeMap;
41  import java.util.TreeSet;
42  
43  import java.security.AccessController;
44  
45  import javax.management.ObjectName;
46  import javax.management.MBeanServer;
47  import javax.management.InstanceNotFoundException;
48  import javax.management.remote.JMXConnectorFactory;
49  import javax.management.remote.JMXConnectorServerFactory;
50  import com.sun.jmx.mbeanserver.GetPropertyAction;
51  import com.sun.jmx.remote.security.NotificationAccessController;
52  import javax.management.remote.JMXConnector;
53  import javax.management.remote.JMXConnectorServer;
54  
55  public class EnvHelp {
56  
57      /**
58       * <p>Name of the attribute that specifies a default class loader
59       * object.
60       * The value associated with this attribute is a ClassLoader object</p>
61       */
62      private static final String DEFAULT_CLASS_LOADER =
63          JMXConnectorFactory.DEFAULT_CLASS_LOADER;
64  
65      /**
66       * <p>Name of the attribute that specifies a default class loader
67       *    ObjectName.
68       * The value associated with this attribute is an ObjectName object</p>
69       */
70      private static final String DEFAULT_CLASS_LOADER_NAME =
71          JMXConnectorServerFactory.DEFAULT_CLASS_LOADER_NAME;
72  
73      /**
74       * Get the Connector Server default class loader.
75       * <p>
76       * Returns:
77       * <p>
78       * <ul>
79       * <li>
80       *     The ClassLoader object found in <var>env</var> for
81       *     <code>jmx.remote.default.class.loader</code>, if any.
82       * </li>
83       * <li>
84       *     The ClassLoader pointed to by the ObjectName found in
85       *     <var>env</var> for <code>jmx.remote.default.class.loader.name</code>,
86       *     and registered in <var>mbs</var> if any.
87       * </li>
88       * <li>
89       *     The current thread's context classloader otherwise.
90       * </li>
91       * </ul>
92       *
93       * @param env Environment attributes.
94       * @param mbs The MBeanServer for which the connector server provides
95       * remote access.
96       *
97       * @return the connector server's default class loader.
98       *
99       * @exception IllegalArgumentException if one of the following is true:
100      * <ul>
101      * <li>both
102      *     <code>jmx.remote.default.class.loader</code> and
103      *     <code>jmx.remote.default.class.loader.name</code> are specified,
104      * </li>
105      * <li>or
106      *     <code>jmx.remote.default.class.loader</code> is not
107      *     an instance of {@link ClassLoader},
108      * </li>
109      * <li>or
110      *     <code>jmx.remote.default.class.loader.name</code> is not
111      *     an instance of {@link ObjectName},
112      * </li>
113      * <li>or
114      *     <code>jmx.remote.default.class.loader.name</code> is specified
115      *     but <var>mbs</var> is null.
116      * </li>
117      * @exception InstanceNotFoundException if
118      * <code>jmx.remote.default.class.loader.name</code> is specified
119      * and the ClassLoader MBean is not found in <var>mbs</var>.
120      */
121     public static ClassLoader resolveServerClassLoader(Map<String, ?> env,
122                                                        MBeanServer mbs)
123         throws InstanceNotFoundException {
124 
125         if (env == null)
126             return Thread.currentThread().getContextClassLoader();
127 
128         Object loader = env.get(DEFAULT_CLASS_LOADER);
129         Object name   = env.get(DEFAULT_CLASS_LOADER_NAME);
130 
131         if (loader != null && name != null) {
132             final String msg = "Only one of " +
133                 DEFAULT_CLASS_LOADER + " or " +
134                 DEFAULT_CLASS_LOADER_NAME +
135                 " should be specified.";
136             throw new IllegalArgumentException(msg);
137         }
138 
139         if (loader == null && name == null)
140             return Thread.currentThread().getContextClassLoader();
141 
142         if (loader != null) {
143             if (loader instanceof ClassLoader) {
144                 return (ClassLoader) loader;
145             } else {
146                 final String msg =
147                     "ClassLoader object is not an instance of " +
148                     ClassLoader.class.getName() + " : " +
149                     loader.getClass().getName();
150                 throw new IllegalArgumentException(msg);
151             }
152         }
153 
154         ObjectName on;
155         if (name instanceof ObjectName) {
156             on = (ObjectName) name;
157         } else {
158             final String msg =
159                 "ClassLoader name is not an instance of " +
160                 ObjectName.class.getName() + " : " +
161                 name.getClass().getName();
162             throw new IllegalArgumentException(msg);
163         }
164 
165         if (mbs == null)
166             throw new IllegalArgumentException("Null MBeanServer object");
167 
168         return mbs.getClassLoader(on);
169     }
170 
171     /**
172      * Get the Connector Client default class loader.
173      * <p>
174      * Returns:
175      * <p>
176      * <ul>
177      * <li>
178      *     The ClassLoader object found in <var>env</var> for
179      *     <code>jmx.remote.default.class.loader</code>, if any.
180      * </li>
181      * <li>The <tt>Thread.currentThread().getContextClassLoader()</tt>
182      *     otherwise.
183      * </li>
184      * </ul>
185      * <p>
186      * Usually a Connector Client will call
187      * <pre>
188      * ClassLoader dcl = EnvHelp.resolveClientClassLoader(env);
189      * </pre>
190      * in its <code>connect(Map env)</code> method.
191      *
192      * @return The connector client default class loader.
193      *
194      * @exception IllegalArgumentException if
195      * <code>jmx.remote.default.class.loader</code> is specified
196      * and is not an instance of {@link ClassLoader}.
197      */
198     public static ClassLoader resolveClientClassLoader(Map<String, ?> env) {
199 
200         if (env == null)
201             return Thread.currentThread().getContextClassLoader();
202 
203         Object loader = env.get(DEFAULT_CLASS_LOADER);
204 
205         if (loader == null)
206             return Thread.currentThread().getContextClassLoader();
207 
208         if (loader instanceof ClassLoader) {
209             return (ClassLoader) loader;
210         } else {
211             final String msg =
212                 "ClassLoader object is not an instance of " +
213                 ClassLoader.class.getName() + " : " +
214                 loader.getClass().getName();
215             throw new IllegalArgumentException(msg);
216         }
217     }
218 
219     /**
220      * Initialize the cause field of a {@code Throwable} object.
221      *
222      * @param throwable The {@code Throwable} on which the cause is set.
223      * @param cause The cause to set on the supplied {@code Throwable}.
224      * @return the {@code Throwable} with the cause field initialized.
225      */
226     public static <T extends Throwable> T initCause(T throwable,
227                                                     Throwable cause) {
228         throwable.initCause(cause);
229         return throwable;
230     }
231 
232     /**
233      * Returns the cause field of a {@code Throwable} object.
234      * The cause field can be got only if <var>t</var> has an
235      * {@link Throwable#getCause()} method (JDK Version >= 1.4)
236      * @param t {@code Throwable} on which the cause must be set.
237      * @return the cause if getCause() succeeded and the got value is not
238      * null, otherwise return the <var>t</var>.
239      */
240     public static Throwable getCause(Throwable t) {
241         Throwable ret = t;
242 
243         try {
244             java.lang.reflect.Method getCause =
245                 t.getClass().getMethod("getCause", (Class<?>[]) null);
246             ret = (Throwable)getCause.invoke(t, (Object[]) null);
247 
248         } catch (Exception e) {
249             // OK.
250             // it must be older than 1.4.
251         }
252         return (ret != null) ? ret: t;
253     }
254 
255 
256     /**
257      * <p>Name of the attribute that specifies the size of a notification
258      * buffer for a connector server. The default value is 1000.
259      */
260     public static final String BUFFER_SIZE_PROPERTY =
261         "jmx.remote.x.notification.buffer.size";
262 
263 
264     /**
265      * Returns the size of a notification buffer for a connector server.
266      * The default value is 1000.
267      */
268     public static int getNotifBufferSize(Map<String, ?> env) {
269         int defaultQueueSize = 1000; // default value
270 
271         // keep it for the compability for the fix:
272         // 6174229: Environment parameter should be notification.buffer.size
273         // instead of buffer.size
274         final String oldP = "jmx.remote.x.buffer.size";
275 
276         // the default value re-specified in the system
277         try {
278             GetPropertyAction act = new GetPropertyAction(BUFFER_SIZE_PROPERTY);
279             String s = AccessController.doPrivileged(act);
280             if (s != null) {
281                 defaultQueueSize = Integer.parseInt(s);
282             } else { // try the old one
283                 act = new GetPropertyAction(oldP);
284                 s = AccessController.doPrivileged(act);
285                 if (s != null) {
286                     defaultQueueSize = Integer.parseInt(s);
287                 }
288             }
289         } catch (RuntimeException e) {
290             logger.warning("getNotifBufferSize",
291                            "Can't use System property "+
292                            BUFFER_SIZE_PROPERTY+ ": " + e);
293               logger.debug("getNotifBufferSize", e);
294         }
295 
296         int queueSize = defaultQueueSize;
297 
298         try {
299             if (env.containsKey(BUFFER_SIZE_PROPERTY)) {
300                 queueSize = (int)EnvHelp.getIntegerAttribute(env,BUFFER_SIZE_PROPERTY,
301                                             defaultQueueSize,0,
302                                             Integer.MAX_VALUE);
303             } else { // try the old one
304                 queueSize = (int)EnvHelp.getIntegerAttribute(env,oldP,
305                                             defaultQueueSize,0,
306                                             Integer.MAX_VALUE);
307             }
308         } catch (RuntimeException e) {
309             logger.warning("getNotifBufferSize",
310                            "Can't determine queuesize (using default): "+
311                            e);
312             logger.debug("getNotifBufferSize", e);
313         }
314 
315         return queueSize;
316     }
317 
318     /**
319      * <p>Name of the attribute that specifies the maximum number of
320      * notifications that a client will fetch from its server.. The
321      * value associated with this attribute should be an
322      * <code>Integer</code> object.  The default value is 1000.</p>
323      */
324     public static final String MAX_FETCH_NOTIFS =
325         "jmx.remote.x.notification.fetch.max";
326 
327     /**
328      * Returns the maximum notification number which a client will
329      * fetch every time.
330      */
331     public static int getMaxFetchNotifNumber(Map<String, ?> env) {
332         return (int) getIntegerAttribute(env, MAX_FETCH_NOTIFS, 1000, 1,
333                                          Integer.MAX_VALUE);
334     }
335 
336     /**
337      * <p>Name of the attribute that specifies the timeout for a
338      * client to fetch notifications from its server. The value
339      * associated with this attribute should be a <code>Long</code>
340      * object.  The default value is 60000 milliseconds.</p>
341      */
342     public static final String FETCH_TIMEOUT =
343         "jmx.remote.x.notification.fetch.timeout";
344 
345     /**
346      * Returns the timeout for a client to fetch notifications.
347      */
348     public static long getFetchTimeout(Map<String, ?> env) {
349         return getIntegerAttribute(env, FETCH_TIMEOUT, 60000L, 0,
350                 Long.MAX_VALUE);
351     }
352 
353     /**
354      * <p>Name of the attribute that specifies an object that will check
355      * accesses to add/removeNotificationListener and also attempts to
356      * receive notifications.  The value associated with this attribute
357      * should be a <code>NotificationAccessController</code> object.
358      * The default value is null.</p>
359      * This field is not public because of its com.sun dependency.
360      */
361     public static final String NOTIF_ACCESS_CONTROLLER =
362             "com.sun.jmx.remote.notification.access.controller";
363 
364     public static NotificationAccessController getNotificationAccessController(
365             Map<String, ?> env) {
366         return (env == null) ? null :
367             (NotificationAccessController) env.get(NOTIF_ACCESS_CONTROLLER);
368     }
369 
370     /**
371      * Get an integer-valued attribute with name <code>name</code>
372      * from <code>env</code>.  If <code>env</code> is null, or does
373      * not contain an entry for <code>name</code>, return
374      * <code>defaultValue</code>.  The value may be a Number, or it
375      * may be a String that is parsable as a long.  It must be at
376      * least <code>minValue</code> and at most<code>maxValue</code>.
377      *
378      * @throws IllegalArgumentException if <code>env</code> contains
379      * an entry for <code>name</code> but it does not meet the
380      * constraints above.
381      */
382     public static long getIntegerAttribute(Map<String, ?> env, String name,
383                                            long defaultValue, long minValue,
384                                            long maxValue) {
385         final Object o;
386 
387         if (env == null || (o = env.get(name)) == null)
388             return defaultValue;
389 
390         final long result;
391 
392         if (o instanceof Number)
393             result = ((Number) o).longValue();
394         else if (o instanceof String) {
395             result = Long.parseLong((String) o);
396             /* May throw a NumberFormatException, which is an
397                IllegalArgumentException.  */
398         } else {
399             final String msg =
400                 "Attribute " + name + " value must be Integer or String: " + o;
401             throw new IllegalArgumentException(msg);
402         }
403 
404         if (result < minValue) {
405             final String msg =
406                 "Attribute " + name + " value must be at least " + minValue +
407                 ": " + result;
408             throw new IllegalArgumentException(msg);
409         }
410 
411         if (result > maxValue) {
412             final String msg =
413                 "Attribute " + name + " value must be at most " + maxValue +
414                 ": " + result;
415             throw new IllegalArgumentException(msg);
416         }
417 
418         return result;
419     }
420 
421     public static final String DEFAULT_ORB="java.naming.corba.orb";
422 
423     /* Check that all attributes have a key that is a String.
424        Could make further checks, e.g. appropriate types for attributes.  */
425     public static void checkAttributes(Map<?, ?> attributes) {
426         for (Object key : attributes.keySet()) {
427             if (!(key instanceof String)) {
428                 final String msg =
429                     "Attributes contain key that is not a string: " + key;
430                 throw new IllegalArgumentException(msg);
431             }
432         }
433     }
434 
435     /* Return a writable map containing only those attributes that are
436        serializable, and that are not hidden by
437        jmx.remote.x.hidden.attributes or the default list of hidden
438        attributes.  */
439     public static <V> Map<String, V> filterAttributes(Map<String, V> attributes) {
440         if (logger.traceOn()) {
441             logger.trace("filterAttributes", "starts");
442         }
443 
444         SortedMap<String, V> map = new TreeMap<String, V>(attributes);
445         purgeUnserializable(map.values());
446         hideAttributes(map);
447         return map;
448     }
449 
450     /**
451      * Remove from the given Collection any element that is not a
452      * serializable object.
453      */
454     private static void purgeUnserializable(Collection<?> objects) {
455         logger.trace("purgeUnserializable", "starts");
456         ObjectOutputStream oos = null;
457         int i = 0;
458         for (Iterator<?> it = objects.iterator(); it.hasNext(); i++) {
459             Object v = it.next();
460 
461             if (v == null || v instanceof String) {
462                 if (logger.traceOn()) {
463                     logger.trace("purgeUnserializable",
464                                  "Value trivially serializable: " + v);
465                 }
466                 continue;
467             }
468 
469             try {
470                 if (oos == null)
471                     oos = new ObjectOutputStream(new SinkOutputStream());
472                 oos.writeObject(v);
473                 if (logger.traceOn()) {
474                     logger.trace("purgeUnserializable",
475                                  "Value serializable: " + v);
476                 }
477             } catch (IOException e) {
478                 if (logger.traceOn()) {
479                     logger.trace("purgeUnserializable",
480                                  "Value not serializable: " + v + ": " +
481                                  e);
482                 }
483                 it.remove();
484                 oos = null; // ObjectOutputStream invalid after exception
485             }
486         }
487     }
488 
489     /**
490      * The value of this attribute, if present, is a string specifying
491      * what other attributes should not appear in
492      * JMXConnectorServer.getAttributes().  It is a space-separated
493      * list of attribute patterns, where each pattern is either an
494      * attribute name, or an attribute prefix followed by a "*"
495      * character.  The "*" has no special significance anywhere except
496      * at the end of a pattern.  By default, this list is added to the
497      * list defined by {@link #DEFAULT_HIDDEN_ATTRIBUTES} (which
498      * uses the same format).  If the value of this attribute begins
499      * with an "=", then the remainder of the string defines the
500      * complete list of attribute patterns.
501      */
502     public static final String HIDDEN_ATTRIBUTES =
503         "jmx.remote.x.hidden.attributes";
504 
505     /**
506      * Default list of attributes not to show.
507      * @see #HIDDEN_ATTRIBUTES
508      */
509     /* This list is copied directly from the spec, plus
510        java.naming.security.*.  Most of the attributes here would have
511        been eliminated from the map anyway because they are typically
512        not serializable.  But just in case they are, we list them here
513        to conform to the spec.  */
514     public static final String DEFAULT_HIDDEN_ATTRIBUTES =
515         "java.naming.security.* " +
516         "jmx.remote.authenticator " +
517         "jmx.remote.context " +
518         "jmx.remote.default.class.loader " +
519         "jmx.remote.message.connection.server " +
520         "jmx.remote.object.wrapping " +
521         "jmx.remote.rmi.client.socket.factory " +
522         "jmx.remote.rmi.server.socket.factory " +
523         "jmx.remote.sasl.callback.handler " +
524         "jmx.remote.tls.socket.factory " +
525         "jmx.remote.x.access.file " +
526         "jmx.remote.x.password.file ";
527 
528     private static final SortedSet<String> defaultHiddenStrings =
529             new TreeSet<String>();
530     private static final SortedSet<String> defaultHiddenPrefixes =
531             new TreeSet<String>();
532 
533     private static void hideAttributes(SortedMap<String, ?> map) {
534         if (map.isEmpty())
535             return;
536 
537         final SortedSet<String> hiddenStrings;
538         final SortedSet<String> hiddenPrefixes;
539 
540         String hide = (String) map.get(HIDDEN_ATTRIBUTES);
541         if (hide != null) {
542             if (hide.startsWith("="))
543                 hide = hide.substring(1);
544             else
545                 hide += " " + DEFAULT_HIDDEN_ATTRIBUTES;
546             hiddenStrings = new TreeSet<String>();
547             hiddenPrefixes = new TreeSet<String>();
548             parseHiddenAttributes(hide, hiddenStrings, hiddenPrefixes);
549         } else {
550             hide = DEFAULT_HIDDEN_ATTRIBUTES;
551             synchronized (defaultHiddenStrings) {
552                 if (defaultHiddenStrings.isEmpty()) {
553                     parseHiddenAttributes(hide,
554                                           defaultHiddenStrings,
555                                           defaultHiddenPrefixes);
556                 }
557                 hiddenStrings = defaultHiddenStrings;
558                 hiddenPrefixes = defaultHiddenPrefixes;
559             }
560         }
561 
562         /* Construct a string that is greater than any key in the map.
563            Setting a string-to-match or a prefix-to-match to this string
564            guarantees that we will never call next() on the corresponding
565            iterator.  */
566         String sentinelKey = map.lastKey() + "X";
567         Iterator<String> keyIterator = map.keySet().iterator();
568         Iterator<String> stringIterator = hiddenStrings.iterator();
569         Iterator<String> prefixIterator = hiddenPrefixes.iterator();
570 
571         String nextString;
572         if (stringIterator.hasNext())
573             nextString = stringIterator.next();
574         else
575             nextString = sentinelKey;
576         String nextPrefix;
577         if (prefixIterator.hasNext())
578             nextPrefix = prefixIterator.next();
579         else
580             nextPrefix = sentinelKey;
581 
582         /* Read each key in sorted order and, if it matches a string
583            or prefix, remove it. */
584     keys:
585         while (keyIterator.hasNext()) {
586             String key = keyIterator.next();
587 
588             /* Continue through string-match values until we find one
589                that is either greater than the current key, or equal
590                to it.  In the latter case, remove the key.  */
591             int cmp = +1;
592             while ((cmp = nextString.compareTo(key)) < 0) {
593                 if (stringIterator.hasNext())
594                     nextString = stringIterator.next();
595                 else
596                     nextString = sentinelKey;
597             }
598             if (cmp == 0) {
599                 keyIterator.remove();
600                 continue keys;
601             }
602 
603             /* Continue through the prefix values until we find one
604                that is either greater than the current key, or a
605                prefix of it.  In the latter case, remove the key.  */
606             while (nextPrefix.compareTo(key) <= 0) {
607                 if (key.startsWith(nextPrefix)) {
608                     keyIterator.remove();
609                     continue keys;
610                 }
611                 if (prefixIterator.hasNext())
612                     nextPrefix = prefixIterator.next();
613                 else
614                     nextPrefix = sentinelKey;
615             }
616         }
617     }
618 
619     private static void parseHiddenAttributes(String hide,
620                                               SortedSet<String> hiddenStrings,
621                                               SortedSet<String> hiddenPrefixes) {
622         final StringTokenizer tok = new StringTokenizer(hide);
623         while (tok.hasMoreTokens()) {
624             String s = tok.nextToken();
625             if (s.endsWith("*"))
626                 hiddenPrefixes.add(s.substring(0, s.length() - 1));
627             else
628                 hiddenStrings.add(s);
629         }
630     }
631 
632     /**
633      * <p>Name of the attribute that specifies the timeout to keep a
634      * server side connection after answering last client request.
635      * The default value is 120000 milliseconds.</p>
636      */
637     public static final String SERVER_CONNECTION_TIMEOUT =
638         "jmx.remote.x.server.connection.timeout";
639 
640     /**
641      * Returns the server side connection timeout.
642      */
643     public static long getServerConnectionTimeout(Map<String, ?> env) {
644         return getIntegerAttribute(env, SERVER_CONNECTION_TIMEOUT, 120000L,
645                                    0, Long.MAX_VALUE);
646     }
647 
648     /**
649      * <p>Name of the attribute that specifies the period in
650      * millisecond for a client to check its connection.  The default
651      * value is 60000 milliseconds.</p>
652      */
653     public static final String CLIENT_CONNECTION_CHECK_PERIOD =
654         "jmx.remote.x.client.connection.check.period";
655 
656     /**
657      * Returns the client connection check period.
658      */
659     public static long getConnectionCheckPeriod(Map<String, ?> env) {
660         return getIntegerAttribute(env, CLIENT_CONNECTION_CHECK_PERIOD, 60000L,
661                                    0, Long.MAX_VALUE);
662     }
663 
664     /**
665      * Computes a boolean value from a string value retrieved from a
666      * property in the given map.
667      *
668      * @param stringBoolean the string value that must be converted
669      * into a boolean value.
670      *
671      * @return
672      *   <ul>
673      *   <li>{@code false} if {@code stringBoolean} is {@code null}</li>
674      *   <li>{@code false} if
675      *       {@code stringBoolean.equalsIgnoreCase("false")}
676      *       is {@code true}</li>
677      *   <li>{@code true} if
678      *       {@code stringBoolean.equalsIgnoreCase("true")}
679      *       is {@code true}</li>
680      *   </ul>
681      *
682      * @throws IllegalArgumentException if
683      * {@code ((String)env.get(prop)).equalsIgnoreCase("false")} and
684      * {@code ((String)env.get(prop)).equalsIgnoreCase("true")} are
685      * {@code false}.
686      */
687     public static boolean computeBooleanFromString(String stringBoolean) {
688         // returns a default value of 'false' if no property is found...
689         return computeBooleanFromString(stringBoolean,false);
690     }
691 
692     /**
693      * Computes a boolean value from a string value retrieved from a
694      * property in the given map.
695      *
696      * @param stringBoolean the string value that must be converted
697      * into a boolean value.
698      * @param defaultValue a default value to return in case no property
699      *        was defined.
700      *
701      * @return
702      *   <ul>
703      *   <li>{@code defaultValue} if {@code stringBoolean}
704      *   is {@code null}</li>
705      *   <li>{@code false} if
706      *       {@code stringBoolean.equalsIgnoreCase("false")}
707      *       is {@code true}</li>
708      *   <li>{@code true} if
709      *       {@code stringBoolean.equalsIgnoreCase("true")}
710      *       is {@code true}</li>
711      *   </ul>
712      *
713      * @throws IllegalArgumentException if
714      * {@code ((String)env.get(prop)).equalsIgnoreCase("false")} and
715      * {@code ((String)env.get(prop)).equalsIgnoreCase("true")} are
716      * {@code false}.
717      */
718     public static boolean computeBooleanFromString( String stringBoolean, boolean defaultValue) {
719         if (stringBoolean == null)
720             return defaultValue;
721         else if (stringBoolean.equalsIgnoreCase("true"))
722             return true;
723         else if (stringBoolean.equalsIgnoreCase("false"))
724             return false;
725         else
726             throw new IllegalArgumentException(
727                 "Property value must be \"true\" or \"false\" instead of \"" +
728                 stringBoolean + "\"");
729     }
730 
731     /**
732      * Converts a map into a valid hash table, i.e.
733      * it removes all the 'null' values from the map.
734      */
735     public static <K, V> Hashtable<K, V> mapToHashtable(Map<K, V> map) {
736         HashMap<K, V> m = new HashMap<K, V>(map);
737         if (m.containsKey(null)) m.remove(null);
738         for (Iterator<?> i = m.values().iterator(); i.hasNext(); )
739             if (i.next() == null) i.remove();
740         return new Hashtable<K, V>(m);
741     }
742 
743     /**
744      * <p>Name of the attribute that specifies whether a connector server
745      * should not prevent the VM from exiting
746      */
747     public static final String JMX_SERVER_DAEMON = "jmx.remote.x.daemon";
748 
749     /**
750      * Returns true if {@value SERVER_DAEMON} is specified in the {@code env}
751      * as a key and its value is a String and it is equal to true ignoring case.
752      *
753      * @param env
754      * @return
755      */
756     public static boolean isServerDaemon(Map<String, ?> env) {
757         return (env != null) &&
758                 ("true".equalsIgnoreCase((String)env.get(JMX_SERVER_DAEMON)));
759     }
760 
761     private static final class SinkOutputStream extends OutputStream {
762         public void write(byte[] b, int off, int len) {}
763         public void write(int b) {}
764     }
765 
766     private static final ClassLogger logger =
767         new ClassLogger("javax.management.remote.misc", "EnvHelp");
768 }