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