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 * Name of the attribute that specifies a default class loader 59 * object. 60 * The value associated with this attribute is a ClassLoader object. 61 */ 62 private static final String DEFAULT_CLASS_LOADER = 63 JMXConnectorFactory.DEFAULT_CLASS_LOADER; 64 65 /** 66 * Name of the attribute that specifies a default class loader 67 * ObjectName. 68 * The value associated with this attribute is an ObjectName object. 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 * <ul> 78 * <li> 79 * The ClassLoader object found in <var>env</var> for 80 * <code>jmx.remote.default.class.loader</code>, if any. 81 * </li> 82 * <li> 83 * The ClassLoader pointed to by the ObjectName found in 84 * <var>env</var> for <code>jmx.remote.default.class.loader.name</code>, 85 * and registered in <var>mbs</var> if any. 86 * </li> 87 * <li> 88 * The current thread's context classloader otherwise. 89 * </li> 90 * </ul> 91 * 92 * @param env Environment attributes. 93 * @param mbs The MBeanServer for which the connector server provides 94 * remote access. 95 * 96 * @return the connector server's default class loader. 97 * 98 * @exception IllegalArgumentException if one of the following is true: 99 * <ul> 100 * <li>both 101 * <code>jmx.remote.default.class.loader</code> and 102 * <code>jmx.remote.default.class.loader.name</code> are specified, 103 * </li> 104 * <li>or 105 * <code>jmx.remote.default.class.loader</code> is not 106 * an instance of {@link ClassLoader}, 107 * </li> 108 * <li>or 109 * <code>jmx.remote.default.class.loader.name</code> is not 110 * an instance of {@link ObjectName}, 111 * </li> 112 * <li>or 113 * <code>jmx.remote.default.class.loader.name</code> is specified 114 * but <var>mbs</var> is null. 115 * </li> 116 * </ul> 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 * <ul> 176 * <li> 177 * The ClassLoader object found in <var>env</var> for 178 * <code>jmx.remote.default.class.loader</code>, if any. 179 * </li> 180 * <li>The <tt>Thread.currentThread().getContextClassLoader()</tt> 181 * otherwise. 182 * </li> 183 * </ul> 184 * <p> 185 * Usually a Connector Client will call 186 * <pre> 187 * ClassLoader dcl = EnvHelp.resolveClientClassLoader(env); 188 * </pre> 189 * in its <code>connect(Map env)</code> method. 190 * 191 * @return The connector client default class loader. 192 * 193 * @exception IllegalArgumentException if 194 * <code>jmx.remote.default.class.loader</code> is specified 195 * and is not an instance of {@link ClassLoader}. 196 */ 197 public static ClassLoader resolveClientClassLoader(Map<String, ?> env) { 198 199 if (env == null) 200 return Thread.currentThread().getContextClassLoader(); 201 202 Object loader = env.get(DEFAULT_CLASS_LOADER); 203 204 if (loader == null) 205 return Thread.currentThread().getContextClassLoader(); 206 207 if (loader instanceof ClassLoader) { 208 return (ClassLoader) loader; 209 } else { 210 final String msg = 211 "ClassLoader object is not an instance of " + 212 ClassLoader.class.getName() + " : " + 213 loader.getClass().getName(); 214 throw new IllegalArgumentException(msg); 215 } 216 } 217 218 /** 219 * Initialize the cause field of a {@code Throwable} object. 220 * 221 * @param throwable The {@code Throwable} on which the cause is set. 222 * @param cause The cause to set on the supplied {@code Throwable}. 223 * @return the {@code Throwable} with the cause field initialized. 224 */ 225 public static <T extends Throwable> T initCause(T throwable, 226 Throwable cause) { 227 throwable.initCause(cause); 228 return throwable; 229 } 230 231 /** 232 * Returns the cause field of a {@code Throwable} object. 233 * The cause field can be got only if <var>t</var> has an 234 * {@link Throwable#getCause()} method (JDK Version {@literal >=} 1.4) 235 * @param t {@code Throwable} on which the cause must be set. 236 * @return the cause if getCause() succeeded and the got value is not 237 * null, otherwise return the <var>t</var>. 238 */ 239 public static Throwable getCause(Throwable t) { 240 Throwable ret = t; 241 242 try { 243 java.lang.reflect.Method getCause = 244 t.getClass().getMethod("getCause", (Class<?>[]) null); 245 ret = (Throwable)getCause.invoke(t, (Object[]) null); 246 247 } catch (Exception e) { 248 // OK. 249 // it must be older than 1.4. 250 } 251 return (ret != null) ? ret: t; 252 } 253 254 255 /** 256 * Name of the attribute that specifies the size of a notification 257 * buffer for a connector server. The default value is 1000. 258 */ 259 public static final String BUFFER_SIZE_PROPERTY = 260 "jmx.remote.x.notification.buffer.size"; 261 262 263 /** 264 * Returns the size of a notification buffer for a connector server. 265 * The default value is 1000. 266 */ 267 public static int getNotifBufferSize(Map<String, ?> env) { 268 int defaultQueueSize = 1000; // default value 269 270 // keep it for the compability for the fix: 271 // 6174229: Environment parameter should be notification.buffer.size 272 // instead of buffer.size 273 final String oldP = "jmx.remote.x.buffer.size"; 274 275 // the default value re-specified in the system 276 try { 277 GetPropertyAction act = new GetPropertyAction(BUFFER_SIZE_PROPERTY); 278 String s = AccessController.doPrivileged(act); 279 if (s != null) { 280 defaultQueueSize = Integer.parseInt(s); 281 } else { // try the old one 282 act = new GetPropertyAction(oldP); 283 s = AccessController.doPrivileged(act); 284 if (s != null) { 285 defaultQueueSize = Integer.parseInt(s); 286 } 287 } 288 } catch (RuntimeException e) { 289 logger.warning("getNotifBufferSize", 290 "Can't use System property "+ 291 BUFFER_SIZE_PROPERTY+ ": " + e); 292 logger.debug("getNotifBufferSize", e); 293 } 294 295 int queueSize = defaultQueueSize; 296 297 try { 298 if (env.containsKey(BUFFER_SIZE_PROPERTY)) { 299 queueSize = (int)EnvHelp.getIntegerAttribute(env,BUFFER_SIZE_PROPERTY, 300 defaultQueueSize,0, 301 Integer.MAX_VALUE); 302 } else { // try the old one 303 queueSize = (int)EnvHelp.getIntegerAttribute(env,oldP, 304 defaultQueueSize,0, 305 Integer.MAX_VALUE); 306 } 307 } catch (RuntimeException e) { 308 logger.warning("getNotifBufferSize", 309 "Can't determine queuesize (using default): "+ 310 e); 311 logger.debug("getNotifBufferSize", e); 312 } 313 314 return queueSize; 315 } 316 317 /** 318 * Name of the attribute that specifies the maximum number of 319 * notifications that a client will fetch from its server. The 320 * value associated with this attribute should be an 321 * {@code Integer} object. The default value is 1000. 322 */ 323 public static final String MAX_FETCH_NOTIFS = 324 "jmx.remote.x.notification.fetch.max"; 325 326 /** 327 * Returns the maximum notification number which a client will 328 * fetch every time. 329 */ 330 public static int getMaxFetchNotifNumber(Map<String, ?> env) { 331 return (int) getIntegerAttribute(env, MAX_FETCH_NOTIFS, 1000, 1, 332 Integer.MAX_VALUE); 333 } 334 335 /** 336 * Name of the attribute that specifies the timeout for a 337 * client to fetch notifications from its server. The value 338 * associated with this attribute should be a <code>Long</code> 339 * object. The default value is 60000 milliseconds. 340 */ 341 public static final String FETCH_TIMEOUT = 342 "jmx.remote.x.notification.fetch.timeout"; 343 344 /** 345 * Returns the timeout for a client to fetch notifications. 346 */ 347 public static long getFetchTimeout(Map<String, ?> env) { 348 return getIntegerAttribute(env, FETCH_TIMEOUT, 60000L, 0, 349 Long.MAX_VALUE); 350 } 351 352 /** 353 * Name of the attribute that specifies an object that will check 354 * accesses to add/removeNotificationListener and also attempts to 355 * receive notifications. The value associated with this attribute 356 * should be a <code>NotificationAccessController</code> object. 357 * The default value is null. 358 * <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 * 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. 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 * 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. 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 * 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 JMX_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 }