1 /* 2 * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 /* 26 * @author IBM Corp. 27 * 28 * Copyright IBM Corp. 1999-2000. All rights reserved. 29 */ 30 31 package javax.management.modelmbean; 32 33 import static com.sun.jmx.defaults.JmxProperties.MODELMBEAN_LOGGER; 34 import static com.sun.jmx.mbeanserver.Util.cast; 35 import com.sun.jmx.mbeanserver.GetPropertyAction; 36 import com.sun.jmx.mbeanserver.Util; 37 38 import java.io.IOException; 39 import java.io.ObjectInputStream; 40 import java.io.ObjectOutputStream; 41 import java.io.ObjectStreamField; 42 43 import java.lang.reflect.Constructor; 44 45 import java.security.AccessController; 46 import java.util.HashMap; 47 import java.util.Iterator; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.Set; 51 import java.util.SortedMap; 52 import java.util.StringTokenizer; 53 import java.util.TreeMap; 54 import java.lang.System.Logger.Level; 55 56 import javax.management.Descriptor; 57 import javax.management.ImmutableDescriptor; 58 import javax.management.MBeanException; 59 import javax.management.RuntimeOperationsException; 60 61 import sun.reflect.misc.ReflectUtil; 62 63 /** 64 * This class represents the metadata set for a ModelMBean element. A 65 * descriptor is part of the ModelMBeanInfo, 66 * ModelMBeanNotificationInfo, ModelMBeanAttributeInfo, 67 * ModelMBeanConstructorInfo, and ModelMBeanParameterInfo. 68 * <P> 69 * A descriptor consists of a collection of fields. Each field is in 70 * fieldname=fieldvalue format. Field names are not case sensitive, 71 * case will be preserved on field values. 72 * <P> 73 * All field names and values are not predefined. New fields can be 74 * defined and added by any program. Some fields have been predefined 75 * for consistency of implementation and support by the 76 * ModelMBeanInfo, ModelMBeanAttributeInfo, ModelMBeanConstructorInfo, 77 * ModelMBeanNotificationInfo, ModelMBeanOperationInfo and ModelMBean 78 * classes. 79 * 80 * <p>The <b>serialVersionUID</b> of this class is <code>-6292969195866300415L</code>. 81 * 82 * @since 1.5 83 */ 84 @SuppressWarnings("serial") // serialVersionUID not constant 85 public class DescriptorSupport 86 implements javax.management.Descriptor 87 { 88 89 // Serialization compatibility stuff: 90 // Two serial forms are supported in this class. The selected form depends 91 // on system property "jmx.serial.form": 92 // - "1.0" for JMX 1.0 93 // - any other value for JMX 1.1 and higher 94 // 95 // Serial version for old serial form 96 private static final long oldSerialVersionUID = 8071560848919417985L; 97 // 98 // Serial version for new serial form 99 private static final long newSerialVersionUID = -6292969195866300415L; 100 // 101 // Serializable fields in old serial form 102 private static final ObjectStreamField[] oldSerialPersistentFields = 103 { 104 new ObjectStreamField("descriptor", HashMap.class), 105 new ObjectStreamField("currClass", String.class) 106 }; 107 // 108 // Serializable fields in new serial form 109 private static final ObjectStreamField[] newSerialPersistentFields = 110 { 111 new ObjectStreamField("descriptor", HashMap.class) 112 }; 113 // 114 // Actual serial version and serial form 115 private static final long serialVersionUID; 116 /** 117 * @serialField descriptor HashMap The collection of fields representing this descriptor 118 */ 119 private static final ObjectStreamField[] serialPersistentFields; 120 private static final String serialForm; 121 static { 122 String form = null; 123 boolean compat = false; 124 try { 125 GetPropertyAction act = new GetPropertyAction("jmx.serial.form"); 126 form = AccessController.doPrivileged(act); 127 compat = "1.0".equals(form); // form may be null 128 } catch (Exception e) { 129 // OK: No compat with 1.0 130 } 131 serialForm = form; 132 if (compat) { 133 serialPersistentFields = oldSerialPersistentFields; 134 serialVersionUID = oldSerialVersionUID; 135 } else { 136 serialPersistentFields = newSerialPersistentFields; 137 serialVersionUID = newSerialVersionUID; 138 } 139 } 140 // 141 // END Serialization compatibility stuff 142 143 /* Spec says that field names are case-insensitive, but that case 144 is preserved. This means that we need to be able to map from a 145 name that may differ in case to the actual name that is used in 146 the HashMap. Thus, descriptorMap is a TreeMap with a Comparator 147 that ignores case. 148 149 Previous versions of this class had a field called "descriptor" 150 of type HashMap where the keys were directly Strings. This is 151 hard to reconcile with the required semantics, so we fabricate 152 that field virtually during serialization and deserialization 153 but keep the real information in descriptorMap. 154 */ 155 private transient SortedMap<String, Object> descriptorMap; 156 157 private static final String currClass = "DescriptorSupport"; 158 159 160 /** 161 * Descriptor default constructor. 162 * Default initial descriptor size is 20. It will grow as needed.<br> 163 * Note that the created empty descriptor is not a valid descriptor 164 * (the method {@link #isValid isValid} returns <CODE>false</CODE>) 165 */ 166 public DescriptorSupport() { 167 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 168 MODELMBEAN_LOGGER.log(Level.TRACE, "Constructor"); 169 } 170 init(null); 171 } 172 173 /** 174 * Descriptor constructor. Takes as parameter the initial 175 * capacity of the Map that stores the descriptor fields. 176 * Capacity will grow as needed.<br> Note that the created empty 177 * descriptor is not a valid descriptor (the method {@link 178 * #isValid isValid} returns <CODE>false</CODE>). 179 * 180 * @param initNumFields The initial capacity of the Map that 181 * stores the descriptor fields. 182 * 183 * @exception RuntimeOperationsException for illegal value for 184 * initNumFields (<= 0) 185 * @exception MBeanException Wraps a distributed communication Exception. 186 */ 187 public DescriptorSupport(int initNumFields) 188 throws MBeanException, RuntimeOperationsException { 189 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 190 MODELMBEAN_LOGGER.log(Level.TRACE, 191 "Descriptor(initNumFields = " + initNumFields + ") " + 192 "Constructor"); 193 } 194 if (initNumFields <= 0) { 195 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 196 MODELMBEAN_LOGGER.log(Level.TRACE, 197 "Illegal arguments: initNumFields <= 0"); 198 } 199 final String msg = 200 "Descriptor field limit invalid: " + initNumFields; 201 final RuntimeException iae = new IllegalArgumentException(msg); 202 throw new RuntimeOperationsException(iae, msg); 203 } 204 init(null); 205 } 206 207 /** 208 * Descriptor constructor taking a Descriptor as parameter. 209 * Creates a new descriptor initialized to the values of the 210 * descriptor passed in parameter. 211 * 212 * @param inDescr the descriptor to be used to initialize the 213 * constructed descriptor. If it is null or contains no descriptor 214 * fields, an empty Descriptor will be created. 215 */ 216 public DescriptorSupport(DescriptorSupport inDescr) { 217 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 218 MODELMBEAN_LOGGER.log(Level.TRACE, 219 "Descriptor(Descriptor) Constructor"); 220 } 221 if (inDescr == null) 222 init(null); 223 else 224 init(inDescr.descriptorMap); 225 } 226 227 228 /** 229 * <p>Descriptor constructor taking an XML String.</p> 230 * 231 * <p>The format of the XML string is not defined, but an 232 * implementation must ensure that the string returned by 233 * {@link #toXMLString() toXMLString()} on an existing 234 * descriptor can be used to instantiate an equivalent 235 * descriptor using this constructor.</p> 236 * 237 * <p>In this implementation, all field values will be created 238 * as Strings. If the field values are not Strings, the 239 * programmer will have to reset or convert these fields 240 * correctly.</p> 241 * 242 * @param inStr An XML-formatted string used to populate this 243 * Descriptor. The format is not defined, but any 244 * implementation must ensure that the string returned by 245 * method {@link #toXMLString toXMLString} on an existing 246 * descriptor can be used to instantiate an equivalent 247 * descriptor when instantiated using this constructor. 248 * 249 * @exception RuntimeOperationsException If the String inStr 250 * passed in parameter is null 251 * @exception XMLParseException XML parsing problem while parsing 252 * the input String 253 * @exception MBeanException Wraps a distributed communication Exception. 254 */ 255 /* At some stage we should rewrite this code to be cleverer. Using 256 a StringTokenizer as we do means, first, that we accept a lot of 257 bogus strings without noticing they are bogus, and second, that we 258 split the string being parsed at characters like > even if they 259 occur in the middle of a field value. */ 260 public DescriptorSupport(String inStr) 261 throws MBeanException, RuntimeOperationsException, 262 XMLParseException { 263 /* parse an XML-formatted string and populate internal 264 * structure with it */ 265 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 266 MODELMBEAN_LOGGER.log(Level.TRACE, 267 "Descriptor(String = '" + inStr + "') Constructor"); 268 } 269 if (inStr == null) { 270 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 271 MODELMBEAN_LOGGER.log(Level.TRACE, 272 "Descriptor(String = null) Illegal arguments"); 273 } 274 final String msg = "String in parameter is null"; 275 final RuntimeException iae = new IllegalArgumentException(msg); 276 throw new RuntimeOperationsException(iae, msg); 277 } 278 279 final String lowerInStr = inStr.toLowerCase(Locale.ENGLISH); 280 if (!lowerInStr.startsWith("<descriptor>") 281 || !lowerInStr.endsWith("</descriptor>")) { 282 throw new XMLParseException("No <descriptor>, </descriptor> pair"); 283 } 284 285 // parse xmlstring into structures 286 init(null); 287 // create dummy descriptor: should have same size 288 // as number of fields in xmlstring 289 // loop through structures and put them in descriptor 290 291 StringTokenizer st = new StringTokenizer(inStr, "<> \t\n\r\f"); 292 293 boolean inFld = false; 294 boolean inDesc = false; 295 String fieldName = null; 296 String fieldValue = null; 297 298 299 while (st.hasMoreTokens()) { // loop through tokens 300 String tok = st.nextToken(); 301 302 if (tok.equalsIgnoreCase("FIELD")) { 303 inFld = true; 304 } else if (tok.equalsIgnoreCase("/FIELD")) { 305 if ((fieldName != null) && (fieldValue != null)) { 306 fieldName = 307 fieldName.substring(fieldName.indexOf('"') + 1, 308 fieldName.lastIndexOf('"')); 309 final Object fieldValueObject = 310 parseQuotedFieldValue(fieldValue); 311 setField(fieldName, fieldValueObject); 312 } 313 fieldName = null; 314 fieldValue = null; 315 inFld = false; 316 } else if (tok.equalsIgnoreCase("DESCRIPTOR")) { 317 inDesc = true; 318 } else if (tok.equalsIgnoreCase("/DESCRIPTOR")) { 319 inDesc = false; 320 fieldName = null; 321 fieldValue = null; 322 inFld = false; 323 } else if (inFld && inDesc) { 324 // want kw=value, eg, name="myname" value="myvalue" 325 int eq_separator = tok.indexOf('='); 326 if (eq_separator > 0) { 327 String kwPart = tok.substring(0,eq_separator); 328 String valPart = tok.substring(eq_separator+1); 329 if (kwPart.equalsIgnoreCase("NAME")) 330 fieldName = valPart; 331 else if (kwPart.equalsIgnoreCase("VALUE")) 332 fieldValue = valPart; 333 else { // xml parse exception 334 final String msg = 335 "Expected `name' or `value', got `" + tok + "'"; 336 throw new XMLParseException(msg); 337 } 338 } else { // xml parse exception 339 final String msg = 340 "Expected `keyword=value', got `" + tok + "'"; 341 throw new XMLParseException(msg); 342 } 343 } 344 } // while tokens 345 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 346 MODELMBEAN_LOGGER.log(Level.TRACE, 347 "Descriptor(XMLString) Exit"); 348 } 349 } 350 351 /** 352 * Constructor taking field names and field values. Neither array 353 * can be null. 354 * 355 * @param fieldNames String array of field names. No elements of 356 * this array can be null. 357 * @param fieldValues Object array of the corresponding field 358 * values. Elements of the array can be null. The 359 * <code>fieldValue</code> must be valid for the 360 * <code>fieldName</code> (as defined in method {@link #isValid 361 * isValid}) 362 * 363 * <p>Note: array sizes of parameters should match. If both arrays 364 * are empty, then an empty descriptor is created.</p> 365 * 366 * @exception RuntimeOperationsException for illegal value for 367 * field Names or field Values. The array lengths must be equal. 368 * If the descriptor construction fails for any reason, this 369 * exception will be thrown. 370 * 371 */ 372 public DescriptorSupport(String[] fieldNames, Object[] fieldValues) 373 throws RuntimeOperationsException { 374 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 375 MODELMBEAN_LOGGER.log(Level.TRACE, 376 "Descriptor(fieldNames,fieldObjects) Constructor"); 377 } 378 379 if ((fieldNames == null) || (fieldValues == null) || 380 (fieldNames.length != fieldValues.length)) { 381 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 382 MODELMBEAN_LOGGER.log(Level.TRACE, 383 "Descriptor(fieldNames,fieldObjects)" + 384 " Illegal arguments"); 385 } 386 387 final String msg = 388 "Null or invalid fieldNames or fieldValues"; 389 final RuntimeException iae = new IllegalArgumentException(msg); 390 throw new RuntimeOperationsException(iae, msg); 391 } 392 393 /* populate internal structure with fields */ 394 init(null); 395 for (int i=0; i < fieldNames.length; i++) { 396 // setField will throw an exception if a fieldName is be null. 397 // the fieldName and fieldValue will be validated in setField. 398 setField(fieldNames[i], fieldValues[i]); 399 } 400 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 401 MODELMBEAN_LOGGER.log(Level.TRACE, 402 "Descriptor(fieldNames,fieldObjects) Exit"); 403 } 404 } 405 406 /** 407 * Constructor taking fields in the <i>fieldName=fieldValue</i> 408 * format. 409 * 410 * @param fields String array with each element containing a 411 * field name and value. If this array is null or empty, then the 412 * default constructor will be executed. Null strings or empty 413 * strings will be ignored. 414 * 415 * <p>All field values should be Strings. If the field values are 416 * not Strings, the programmer will have to reset or convert these 417 * fields correctly. 418 * 419 * <p>Note: Each string should be of the form 420 * <i>fieldName=fieldValue</i>. The field name 421 * ends at the first {@code =} character; for example if the String 422 * is {@code a=b=c} then the field name is {@code a} and its value 423 * is {@code b=c}. 424 * 425 * @exception RuntimeOperationsException for illegal value for 426 * field Names or field Values. The field must contain an 427 * "=". "=fieldValue", "fieldName", and "fieldValue" are illegal. 428 * FieldName cannot be null. "fieldName=" will cause the value to 429 * be null. If the descriptor construction fails for any reason, 430 * this exception will be thrown. 431 * 432 */ 433 public DescriptorSupport(String... fields) 434 { 435 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 436 MODELMBEAN_LOGGER.log(Level.TRACE, 437 "Descriptor(String... fields) Constructor"); 438 } 439 init(null); 440 if (( fields == null ) || ( fields.length == 0)) 441 return; 442 443 init(null); 444 445 for (int i=0; i < fields.length; i++) { 446 if ((fields[i] == null) || (fields[i].isEmpty())) { 447 continue; 448 } 449 int eq_separator = fields[i].indexOf('='); 450 if (eq_separator < 0) { 451 // illegal if no = or is first character 452 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 453 MODELMBEAN_LOGGER.log(Level.TRACE, 454 "Descriptor(String... fields) " + 455 "Illegal arguments: field does not have " + 456 "'=' as a name and value separator"); 457 } 458 final String msg = "Field in invalid format: no equals sign"; 459 final RuntimeException iae = new IllegalArgumentException(msg); 460 throw new RuntimeOperationsException(iae, msg); 461 } 462 463 String fieldName = fields[i].substring(0,eq_separator); 464 String fieldValue = null; 465 if (eq_separator < fields[i].length()) { 466 // = is not in last character 467 fieldValue = fields[i].substring(eq_separator+1); 468 } 469 470 if (fieldName.isEmpty()) { 471 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 472 MODELMBEAN_LOGGER.log(Level.TRACE, 473 "Descriptor(String... fields) " + 474 "Illegal arguments: fieldName is empty"); 475 } 476 477 final String msg = "Field in invalid format: no fieldName"; 478 final RuntimeException iae = new IllegalArgumentException(msg); 479 throw new RuntimeOperationsException(iae, msg); 480 } 481 482 setField(fieldName,fieldValue); 483 } 484 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 485 MODELMBEAN_LOGGER.log(Level.TRACE, 486 "Descriptor(String... fields) Exit"); 487 } 488 } 489 490 private void init(Map<String, ?> initMap) { 491 descriptorMap = 492 new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); 493 if (initMap != null) 494 descriptorMap.putAll(initMap); 495 } 496 497 // Implementation of the Descriptor interface 498 499 500 public synchronized Object getFieldValue(String fieldName) 501 throws RuntimeOperationsException { 502 503 if ((fieldName == null) || (fieldName.isEmpty())) { 504 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 505 MODELMBEAN_LOGGER.log(Level.TRACE, 506 "Illegal arguments: null field name"); 507 } 508 final String msg = "Fieldname requested is null"; 509 final RuntimeException iae = new IllegalArgumentException(msg); 510 throw new RuntimeOperationsException(iae, msg); 511 } 512 Object retValue = descriptorMap.get(fieldName); 513 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 514 MODELMBEAN_LOGGER.log(Level.TRACE, 515 "getFieldValue(String fieldName = " + fieldName + ") " + 516 "Returns '" + retValue + "'"); 517 } 518 return(retValue); 519 } 520 521 public synchronized void setField(String fieldName, Object fieldValue) 522 throws RuntimeOperationsException { 523 524 // field name cannot be null or empty 525 if ((fieldName == null) || (fieldName.isEmpty())) { 526 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 527 MODELMBEAN_LOGGER.log(Level.TRACE, 528 "Illegal arguments: null or empty field name"); 529 } 530 531 final String msg = "Field name to be set is null or empty"; 532 final RuntimeException iae = new IllegalArgumentException(msg); 533 throw new RuntimeOperationsException(iae, msg); 534 } 535 536 if (!validateField(fieldName, fieldValue)) { 537 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 538 MODELMBEAN_LOGGER.log(Level.TRACE, 539 "Illegal arguments"); 540 } 541 542 final String msg = 543 "Field value invalid: " + fieldName + "=" + fieldValue; 544 final RuntimeException iae = new IllegalArgumentException(msg); 545 throw new RuntimeOperationsException(iae, msg); 546 } 547 548 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 549 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry: setting '" 550 + fieldName + "' to '" + fieldValue + "'"); 551 } 552 553 // Since we do not remove any existing entry with this name, 554 // the field will preserve whatever case it had, ignoring 555 // any difference there might be in fieldName. 556 descriptorMap.put(fieldName, fieldValue); 557 } 558 559 public synchronized String[] getFields() { 560 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 561 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 562 } 563 int numberOfEntries = descriptorMap.size(); 564 565 String[] responseFields = new String[numberOfEntries]; 566 Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet(); 567 568 int i = 0; 569 570 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 571 MODELMBEAN_LOGGER.log(Level.TRACE, 572 "Returning " + numberOfEntries + " fields"); 573 } 574 for (Iterator<Map.Entry<String, Object>> iter = returnedSet.iterator(); 575 iter.hasNext(); i++) { 576 Map.Entry<String, Object> currElement = iter.next(); 577 578 if (currElement == null) { 579 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 580 MODELMBEAN_LOGGER.log(Level.TRACE, 581 "Element is null"); 582 } 583 } else { 584 Object currValue = currElement.getValue(); 585 if (currValue == null) { 586 responseFields[i] = currElement.getKey() + "="; 587 } else { 588 if (currValue instanceof java.lang.String) { 589 responseFields[i] = 590 currElement.getKey() + "=" + currValue.toString(); 591 } else { 592 responseFields[i] = 593 currElement.getKey() + "=(" + 594 currValue.toString() + ")"; 595 } 596 } 597 } 598 } 599 600 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 601 MODELMBEAN_LOGGER.log(Level.TRACE, "Exit"); 602 } 603 604 return responseFields; 605 } 606 607 public synchronized String[] getFieldNames() { 608 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 609 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 610 } 611 int numberOfEntries = descriptorMap.size(); 612 613 String[] responseFields = new String[numberOfEntries]; 614 Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet(); 615 616 int i = 0; 617 618 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 619 MODELMBEAN_LOGGER.log(Level.TRACE, 620 "Returning " + numberOfEntries + " fields"); 621 } 622 623 for (Iterator<Map.Entry<String, Object>> iter = returnedSet.iterator(); 624 iter.hasNext(); i++) { 625 Map.Entry<String, Object> currElement = iter.next(); 626 627 if (( currElement == null ) || (currElement.getKey() == null)) { 628 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 629 MODELMBEAN_LOGGER.log(Level.TRACE, "Field is null"); 630 } 631 } else { 632 responseFields[i] = currElement.getKey().toString(); 633 } 634 } 635 636 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 637 MODELMBEAN_LOGGER.log(Level.TRACE, "Exit"); 638 } 639 640 return responseFields; 641 } 642 643 644 public synchronized Object[] getFieldValues(String... fieldNames) { 645 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 646 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 647 } 648 // if fieldNames == null return all values 649 // if fieldNames is String[0] return no values 650 651 final int numberOfEntries = 652 (fieldNames == null) ? descriptorMap.size() : fieldNames.length; 653 final Object[] responseFields = new Object[numberOfEntries]; 654 655 int i = 0; 656 657 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 658 MODELMBEAN_LOGGER.log(Level.TRACE, 659 "Returning " + numberOfEntries + " fields"); 660 } 661 662 if (fieldNames == null) { 663 for (Object value : descriptorMap.values()) 664 responseFields[i++] = value; 665 } else { 666 for (i=0; i < fieldNames.length; i++) { 667 if ((fieldNames[i] == null) || (fieldNames[i].isEmpty())) { 668 responseFields[i] = null; 669 } else { 670 responseFields[i] = getFieldValue(fieldNames[i]); 671 } 672 } 673 } 674 675 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 676 MODELMBEAN_LOGGER.log(Level.TRACE, "Exit"); 677 } 678 679 return responseFields; 680 } 681 682 public synchronized void setFields(String[] fieldNames, 683 Object[] fieldValues) 684 throws RuntimeOperationsException { 685 686 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 687 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 688 } 689 690 if ((fieldNames == null) || (fieldValues == null) || 691 (fieldNames.length != fieldValues.length)) { 692 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 693 MODELMBEAN_LOGGER.log(Level.TRACE, 694 "Illegal arguments"); 695 } 696 697 final String msg = "fieldNames and fieldValues are null or invalid"; 698 final RuntimeException iae = new IllegalArgumentException(msg); 699 throw new RuntimeOperationsException(iae, msg); 700 } 701 702 for (int i=0; i < fieldNames.length; i++) { 703 if (( fieldNames[i] == null) || (fieldNames[i].isEmpty())) { 704 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 705 MODELMBEAN_LOGGER.log(Level.TRACE, 706 "Null field name encountered at element " + i); 707 } 708 final String msg = "fieldNames is null or invalid"; 709 final RuntimeException iae = new IllegalArgumentException(msg); 710 throw new RuntimeOperationsException(iae, msg); 711 } 712 setField(fieldNames[i], fieldValues[i]); 713 } 714 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 715 MODELMBEAN_LOGGER.log(Level.TRACE, "Exit"); 716 } 717 } 718 719 /** 720 * Returns a new Descriptor which is a duplicate of the Descriptor. 721 * 722 * @exception RuntimeOperationsException for illegal value for 723 * field Names or field Values. If the descriptor construction 724 * fails for any reason, this exception will be thrown. 725 */ 726 727 @Override 728 public synchronized Object clone() throws RuntimeOperationsException { 729 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 730 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 731 } 732 return(new DescriptorSupport(this)); 733 } 734 735 public synchronized void removeField(String fieldName) { 736 if ((fieldName == null) || (fieldName.isEmpty())) { 737 return; 738 } 739 740 descriptorMap.remove(fieldName); 741 } 742 743 /** 744 * Compares this descriptor to the given object. The objects are equal if 745 * the given object is also a Descriptor, and if the two Descriptors have 746 * the same field names (possibly differing in case) and the same 747 * associated values. The respective values for a field in the two 748 * Descriptors are equal if the following conditions hold: 749 * 750 * <ul> 751 * <li>If one value is null then the other must be too.</li> 752 * <li>If one value is a primitive array then the other must be a primitive 753 * array of the same type with the same elements.</li> 754 * <li>If one value is an object array then the other must be too and 755 * {@link java.util.Arrays#deepEquals(Object[],Object[]) Arrays.deepEquals} 756 * must return true.</li> 757 * <li>Otherwise {@link Object#equals(Object)} must return true.</li> 758 * </ul> 759 * 760 * @param o the object to compare with. 761 * 762 * @return {@code true} if the objects are the same; {@code false} 763 * otherwise. 764 * 765 */ 766 // Note: this Javadoc is copied from javax.management.Descriptor 767 // due to 6369229. 768 @Override 769 public synchronized boolean equals(Object o) { 770 if (o == this) 771 return true; 772 if (! (o instanceof Descriptor)) 773 return false; 774 if (o instanceof ImmutableDescriptor) 775 return o.equals(this); 776 return new ImmutableDescriptor(descriptorMap).equals(o); 777 } 778 779 /** 780 * <p>Returns the hash code value for this descriptor. The hash 781 * code is computed as the sum of the hash codes for each field in 782 * the descriptor. The hash code of a field with name {@code n} 783 * and value {@code v} is {@code n.toLowerCase().hashCode() ^ h}. 784 * Here {@code h} is the hash code of {@code v}, computed as 785 * follows:</p> 786 * 787 * <ul> 788 * <li>If {@code v} is null then {@code h} is 0.</li> 789 * <li>If {@code v} is a primitive array then {@code h} is computed using 790 * the appropriate overloading of {@code java.util.Arrays.hashCode}.</li> 791 * <li>If {@code v} is an object array then {@code h} is computed using 792 * {@link java.util.Arrays#deepHashCode(Object[]) Arrays.deepHashCode}.</li> 793 * <li>Otherwise {@code h} is {@code v.hashCode()}.</li> 794 * </ul> 795 * 796 * @return A hash code value for this object. 797 * 798 */ 799 // Note: this Javadoc is copied from javax.management.Descriptor 800 // due to 6369229. 801 @Override 802 public synchronized int hashCode() { 803 final int size = descriptorMap.size(); 804 // descriptorMap is sorted with a comparator that ignores cases. 805 // 806 return Util.hashCode( 807 descriptorMap.keySet().toArray(new String[size]), 808 descriptorMap.values().toArray(new Object[size])); 809 } 810 811 /** 812 * Returns true if all of the fields have legal values given their 813 * names. 814 * <P> 815 * This implementation does not support interoperating with a directory 816 * or lookup service. Thus, conforming to the specification, no checking is 817 * done on the <i>"export"</i> field. 818 * <P> 819 * Otherwise this implementation returns false if: 820 * <UL> 821 * <LI> name and descriptorType fieldNames are not defined, or 822 * null, or empty, or not String 823 * <LI> class, role, getMethod, setMethod fieldNames, if defined, 824 * are null or not String 825 * <LI> persistPeriod, currencyTimeLimit, lastUpdatedTimeStamp, 826 * lastReturnedTimeStamp if defined, are null, or not a Numeric 827 * String or not a Numeric Value {@literal >= -1} 828 * <LI> log fieldName, if defined, is null, or not a Boolean or 829 * not a String with value "t", "f", "true", "false". These String 830 * values must not be case sensitive. 831 * <LI> visibility fieldName, if defined, is null, or not a 832 * Numeric String or a not Numeric Value {@literal >= 1 and <= 4} 833 * <LI> severity fieldName, if defined, is null, or not a Numeric 834 * String or not a Numeric Value {@literal >= 0 and <= 6}<br> 835 * <LI> persistPolicy fieldName, if defined, is null, or not one of 836 * the following strings:<br> 837 * "OnUpdate", "OnTimer", "NoMoreOftenThan", "OnUnregister", "Always", 838 * "Never". These String values must not be case sensitive.<br> 839 * </UL> 840 * 841 * @exception RuntimeOperationsException If the validity checking 842 * fails for any reason, this exception will be thrown. 843 */ 844 845 public synchronized boolean isValid() throws RuntimeOperationsException { 846 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 847 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 848 } 849 // verify that the descriptor is valid, by iterating over each field... 850 851 Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet(); 852 853 if (returnedSet == null) { // null descriptor, not valid 854 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 855 MODELMBEAN_LOGGER.log(Level.TRACE, 856 "isValid() Returns false (null set)"); 857 } 858 return false; 859 } 860 // must have a name and descriptor type field 861 String thisName = (String)(this.getFieldValue("name")); 862 String thisDescType = (String)(getFieldValue("descriptorType")); 863 864 if ((thisName == null) || (thisDescType == null) || 865 (thisName.isEmpty()) || (thisDescType.isEmpty())) { 866 return false; 867 } 868 869 // According to the descriptor type we validate the fields contained 870 871 for (Map.Entry<String, Object> currElement : returnedSet) { 872 if (currElement != null) { 873 if (currElement.getValue() != null) { 874 // validate the field valued... 875 if (validateField((currElement.getKey()).toString(), 876 (currElement.getValue()).toString())) { 877 continue; 878 } else { 879 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 880 MODELMBEAN_LOGGER.log(Level.TRACE, 881 "Field " + currElement.getKey() + "=" + 882 currElement.getValue() + " is not valid"); 883 } 884 return false; 885 } 886 } 887 } 888 } 889 890 // fell through, all fields OK 891 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 892 MODELMBEAN_LOGGER.log(Level.TRACE, 893 "isValid() Returns true"); 894 } 895 return true; 896 } 897 898 899 // worker routine for isValid() 900 // name is not null 901 // descriptorType is not null 902 // getMethod and setMethod are not null 903 // persistPeriod is numeric 904 // currencyTimeLimit is numeric 905 // lastUpdatedTimeStamp is numeric 906 // visibility is 1-4 907 // severity is 0-6 908 // log is T or F 909 // role is not null 910 // class is not null 911 // lastReturnedTimeStamp is numeric 912 913 914 private boolean validateField(String fldName, Object fldValue) { 915 if ((fldName == null) || (fldName.isEmpty())) 916 return false; 917 String SfldValue = ""; 918 boolean isAString = false; 919 if ((fldValue != null) && (fldValue instanceof java.lang.String)) { 920 SfldValue = (String) fldValue; 921 isAString = true; 922 } 923 924 boolean nameOrDescriptorType = 925 (fldName.equalsIgnoreCase("Name") || 926 fldName.equalsIgnoreCase("DescriptorType")); 927 if (nameOrDescriptorType || 928 fldName.equalsIgnoreCase("SetMethod") || 929 fldName.equalsIgnoreCase("GetMethod") || 930 fldName.equalsIgnoreCase("Role") || 931 fldName.equalsIgnoreCase("Class")) { 932 if (fldValue == null || !isAString) 933 return false; 934 if (nameOrDescriptorType && SfldValue.isEmpty()) 935 return false; 936 return true; 937 } else if (fldName.equalsIgnoreCase("visibility")) { 938 long v; 939 if ((fldValue != null) && (isAString)) { 940 v = toNumeric(SfldValue); 941 } else if (fldValue instanceof java.lang.Integer) { 942 v = ((Integer)fldValue).intValue(); 943 } else return false; 944 945 if (v >= 1 && v <= 4) 946 return true; 947 else 948 return false; 949 } else if (fldName.equalsIgnoreCase("severity")) { 950 951 long v; 952 if ((fldValue != null) && (isAString)) { 953 v = toNumeric(SfldValue); 954 } else if (fldValue instanceof java.lang.Integer) { 955 v = ((Integer)fldValue).intValue(); 956 } else return false; 957 958 return (v >= 0 && v <= 6); 959 } else if (fldName.equalsIgnoreCase("PersistPolicy")) { 960 return (((fldValue != null) && (isAString)) && 961 ( SfldValue.equalsIgnoreCase("OnUpdate") || 962 SfldValue.equalsIgnoreCase("OnTimer") || 963 SfldValue.equalsIgnoreCase("NoMoreOftenThan") || 964 SfldValue.equalsIgnoreCase("Always") || 965 SfldValue.equalsIgnoreCase("Never") || 966 SfldValue.equalsIgnoreCase("OnUnregister"))); 967 } else if (fldName.equalsIgnoreCase("PersistPeriod") || 968 fldName.equalsIgnoreCase("CurrencyTimeLimit") || 969 fldName.equalsIgnoreCase("LastUpdatedTimeStamp") || 970 fldName.equalsIgnoreCase("LastReturnedTimeStamp")) { 971 972 long v; 973 if ((fldValue != null) && (isAString)) { 974 v = toNumeric(SfldValue); 975 } else if (fldValue instanceof java.lang.Number) { 976 v = ((Number)fldValue).longValue(); 977 } else return false; 978 979 return (v >= -1); 980 } else if (fldName.equalsIgnoreCase("log")) { 981 return ((fldValue instanceof java.lang.Boolean) || 982 (isAString && 983 (SfldValue.equalsIgnoreCase("T") || 984 SfldValue.equalsIgnoreCase("true") || 985 SfldValue.equalsIgnoreCase("F") || 986 SfldValue.equalsIgnoreCase("false") ))); 987 } 988 989 // default to true, it is a field we aren't validating (user etc.) 990 return true; 991 } 992 993 994 995 /** 996 * <p>Returns an XML String representing the descriptor.</p> 997 * 998 * <p>The format is not defined, but an implementation must 999 * ensure that the string returned by this method can be 1000 * used to build an equivalent descriptor when instantiated 1001 * using the constructor {@link #DescriptorSupport(String) 1002 * DescriptorSupport(String inStr)}.</p> 1003 * 1004 * <p>Fields which are not String objects will have toString() 1005 * called on them to create the value. The value will be 1006 * enclosed in parentheses. It is not guaranteed that you can 1007 * reconstruct these objects unless they have been 1008 * specifically set up to support toString() in a meaningful 1009 * format and have a matching constructor that accepts a 1010 * String in the same format.</p> 1011 * 1012 * <p>If the descriptor is empty the following String is 1013 * returned: <Descriptor></Descriptor></p> 1014 * 1015 * @return the XML string. 1016 * 1017 * @exception RuntimeOperationsException for illegal value for 1018 * field Names or field Values. If the XML formatted string 1019 * construction fails for any reason, this exception will be 1020 * thrown. 1021 */ 1022 public synchronized String toXMLString() { 1023 final StringBuilder buf = new StringBuilder("<Descriptor>"); 1024 Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet(); 1025 for (Map.Entry<String, Object> currElement : returnedSet) { 1026 final String name = currElement.getKey(); 1027 Object value = currElement.getValue(); 1028 String valueString = null; 1029 /* Set valueString to non-null if and only if this is a string that 1030 cannot be confused with the encoding of an object. If it 1031 could be so confused (surrounded by parentheses) then we 1032 call makeFieldValue as for any non-String object and end 1033 up with an encoding like "(java.lang.String/(thing))". */ 1034 if (value instanceof String) { 1035 final String svalue = (String) value; 1036 if (!svalue.startsWith("(") || !svalue.endsWith(")")) 1037 valueString = quote(svalue); 1038 } 1039 if (valueString == null) 1040 valueString = makeFieldValue(value); 1041 buf.append("<field name=\"").append(name).append("\" value=\"") 1042 .append(valueString).append("\"></field>"); 1043 } 1044 buf.append("</Descriptor>"); 1045 return buf.toString(); 1046 } 1047 1048 private static final String[] entities = { 1049 " ", 1050 "\""", 1051 "<<", 1052 ">>", 1053 "&&", 1054 "\r ", 1055 "\t	", 1056 "\n ", 1057 "\f", 1058 }; 1059 private static final Map<String,Character> entityToCharMap = 1060 new HashMap<String,Character>(); 1061 private static final String[] charToEntityMap; 1062 1063 static { 1064 char maxChar = 0; 1065 for (int i = 0; i < entities.length; i++) { 1066 final char c = entities[i].charAt(0); 1067 if (c > maxChar) 1068 maxChar = c; 1069 } 1070 charToEntityMap = new String[maxChar + 1]; 1071 for (int i = 0; i < entities.length; i++) { 1072 final char c = entities[i].charAt(0); 1073 final String entity = entities[i].substring(1); 1074 charToEntityMap[c] = entity; 1075 entityToCharMap.put(entity, c); 1076 } 1077 } 1078 1079 private static boolean isMagic(char c) { 1080 return (c < charToEntityMap.length && charToEntityMap[c] != null); 1081 } 1082 1083 /* 1084 * Quote the string so that it will be acceptable to the (String) 1085 * constructor. Since the parsing code in that constructor is fairly 1086 * stupid, we're obliged to quote apparently innocuous characters like 1087 * space, <, and >. In a future version, we should rewrite the parser 1088 * and only quote " plus either \ or & (depending on the quote syntax). 1089 */ 1090 private static String quote(String s) { 1091 boolean found = false; 1092 for (int i = 0; i < s.length(); i++) { 1093 if (isMagic(s.charAt(i))) { 1094 found = true; 1095 break; 1096 } 1097 } 1098 if (!found) 1099 return s; 1100 final StringBuilder buf = new StringBuilder(); 1101 for (int i = 0; i < s.length(); i++) { 1102 char c = s.charAt(i); 1103 if (isMagic(c)) 1104 buf.append(charToEntityMap[c]); 1105 else 1106 buf.append(c); 1107 } 1108 return buf.toString(); 1109 } 1110 1111 private static String unquote(String s) throws XMLParseException { 1112 if (!s.startsWith("\"") || !s.endsWith("\"")) 1113 throw new XMLParseException("Value must be quoted: <" + s + ">"); 1114 final StringBuilder buf = new StringBuilder(); 1115 final int len = s.length() - 1; 1116 for (int i = 1; i < len; i++) { 1117 final char c = s.charAt(i); 1118 final int semi; 1119 final Character quoted; 1120 if (c == '&' 1121 && (semi = s.indexOf(';', i + 1)) >= 0 1122 && ((quoted = entityToCharMap.get(s.substring(i, semi+1))) 1123 != null)) { 1124 buf.append(quoted); 1125 i = semi; 1126 } else 1127 buf.append(c); 1128 } 1129 return buf.toString(); 1130 } 1131 1132 /** 1133 * Make the string that will go inside "..." for a value that is not 1134 * a plain String. 1135 * @throws RuntimeOperationsException if the value cannot be encoded. 1136 */ 1137 private static String makeFieldValue(Object value) { 1138 if (value == null) 1139 return "(null)"; 1140 1141 Class<?> valueClass = value.getClass(); 1142 try { 1143 valueClass.getConstructor(String.class); 1144 } catch (NoSuchMethodException e) { 1145 final String msg = 1146 "Class " + valueClass + " does not have a public " + 1147 "constructor with a single string arg"; 1148 final RuntimeException iae = new IllegalArgumentException(msg); 1149 throw new RuntimeOperationsException(iae, 1150 "Cannot make XML descriptor"); 1151 } catch (SecurityException e) { 1152 // OK: we'll pretend the constructor is there 1153 // too bad if it's not: we'll find out when we try to 1154 // reconstruct the DescriptorSupport 1155 } 1156 1157 final String quotedValueString = quote(value.toString()); 1158 1159 return "(" + valueClass.getName() + "/" + quotedValueString + ")"; 1160 } 1161 1162 /* 1163 * Parse a field value from the XML produced by toXMLString(). 1164 * Given a descriptor XML containing <field name="nnn" value="vvv">, 1165 * the argument to this method will be "vvv" (a string including the 1166 * containing quote characters). If vvv begins and ends with parentheses, 1167 * then it may contain: 1168 * - the characters "null", in which case the result is null; 1169 * - a value of the form "some.class.name/xxx", in which case the 1170 * result is equivalent to `new some.class.name("xxx")'; 1171 * - some other string, in which case the result is that string, 1172 * without the parentheses. 1173 */ 1174 private static Object parseQuotedFieldValue(String s) 1175 throws XMLParseException { 1176 s = unquote(s); 1177 if (s.equalsIgnoreCase("(null)")) 1178 return null; 1179 if (!s.startsWith("(") || !s.endsWith(")")) 1180 return s; 1181 final int slash = s.indexOf('/'); 1182 if (slash < 0) { 1183 // compatibility: old code didn't include class name 1184 return s.substring(1, s.length() - 1); 1185 } 1186 final String className = s.substring(1, slash); 1187 1188 final Constructor<?> constr; 1189 try { 1190 ReflectUtil.checkPackageAccess(className); 1191 final ClassLoader contextClassLoader = 1192 Thread.currentThread().getContextClassLoader(); 1193 final Class<?> c = 1194 Class.forName(className, false, contextClassLoader); 1195 constr = c.getConstructor(new Class<?>[] {String.class}); 1196 } catch (Exception e) { 1197 throw new XMLParseException(e, 1198 "Cannot parse value: <" + s + ">"); 1199 } 1200 final String arg = s.substring(slash + 1, s.length() - 1); 1201 try { 1202 return constr.newInstance(new Object[] {arg}); 1203 } catch (Exception e) { 1204 final String msg = 1205 "Cannot construct instance of " + className + 1206 " with arg: <" + s + ">"; 1207 throw new XMLParseException(e, msg); 1208 } 1209 } 1210 1211 /** 1212 * Returns a human readable string representing the 1213 * descriptor. The string will be in the format of 1214 * "fieldName=fieldValue,fieldName2=fieldValue2,..."<br> 1215 * 1216 * If there are no fields in the descriptor, then an empty String 1217 * is returned.<br> 1218 * 1219 * If a fieldValue is an object then the toString() method is 1220 * called on it and its returned value is used as the value for 1221 * the field enclosed in parenthesis. 1222 * 1223 * @exception RuntimeOperationsException for illegal value for 1224 * field Names or field Values. If the descriptor string fails 1225 * for any reason, this exception will be thrown. 1226 */ 1227 @Override 1228 public synchronized String toString() { 1229 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 1230 MODELMBEAN_LOGGER.log(Level.TRACE, "Entry"); 1231 } 1232 1233 String respStr = ""; 1234 String[] fields = getFields(); 1235 1236 if ((fields == null) || (fields.length == 0)) { 1237 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 1238 MODELMBEAN_LOGGER.log(Level.TRACE, "Empty Descriptor"); 1239 } 1240 return respStr; 1241 } 1242 1243 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 1244 MODELMBEAN_LOGGER.log(Level.TRACE, 1245 "Printing " + fields.length + " fields"); 1246 } 1247 1248 for (int i=0; i < fields.length; i++) { 1249 if (i == (fields.length - 1)) { 1250 respStr = respStr.concat(fields[i]); 1251 } else { 1252 respStr = respStr.concat(fields[i] + ", "); 1253 } 1254 } 1255 1256 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) { 1257 MODELMBEAN_LOGGER.log(Level.TRACE, "Exit returning " + respStr); 1258 } 1259 1260 return respStr; 1261 } 1262 1263 // utility to convert to int, returns -2 if bogus. 1264 1265 private long toNumeric(String inStr) { 1266 try { 1267 return java.lang.Long.parseLong(inStr); 1268 } catch (Exception e) { 1269 return -2; 1270 } 1271 } 1272 1273 1274 /** 1275 * Deserializes a {@link DescriptorSupport} from an {@link 1276 * ObjectInputStream}. 1277 */ 1278 private void readObject(ObjectInputStream in) 1279 throws IOException, ClassNotFoundException { 1280 ObjectInputStream.GetField fields = in.readFields(); 1281 Map<String, Object> descriptor = cast(fields.get("descriptor", null)); 1282 init(null); 1283 if (descriptor != null) { 1284 descriptorMap.putAll(descriptor); 1285 } 1286 } 1287 1288 1289 /** 1290 * Serializes a {@link DescriptorSupport} to an {@link ObjectOutputStream}. 1291 */ 1292 /* If you set jmx.serial.form to "1.2.0" or "1.2.1", then we are 1293 bug-compatible with those versions. Specifically, field names 1294 are forced to lower-case before being written. This 1295 contradicts the spec, which, though it does not mention 1296 serialization explicitly, does say that the case of field names 1297 is preserved. But in 1.2.0 and 1.2.1, this requirement was not 1298 met. Instead, field names in the descriptor map were forced to 1299 lower case. Those versions expect this to have happened to a 1300 descriptor they deserialize and e.g. getFieldValue will not 1301 find a field whose name is spelt with a different case. 1302 */ 1303 private void writeObject(ObjectOutputStream out) throws IOException { 1304 ObjectOutputStream.PutField fields = out.putFields(); 1305 boolean compat = "1.0".equals(serialForm); 1306 if (compat) 1307 fields.put("currClass", currClass); 1308 1309 /* Purge the field "targetObject" from the DescriptorSupport before 1310 * serializing since the referenced object is typically not 1311 * serializable. We do this here rather than purging the "descriptor" 1312 * variable below because that HashMap doesn't do case-insensitivity. 1313 * See CR 6332962. 1314 */ 1315 SortedMap<String, Object> startMap = descriptorMap; 1316 if (startMap.containsKey("targetObject")) { 1317 startMap = new TreeMap<String, Object>(descriptorMap); 1318 startMap.remove("targetObject"); 1319 } 1320 1321 final HashMap<String, Object> descriptor; 1322 if (compat || "1.2.0".equals(serialForm) || 1323 "1.2.1".equals(serialForm)) { 1324 descriptor = new HashMap<String, Object>(); 1325 for (Map.Entry<String, Object> entry : startMap.entrySet()) 1326 descriptor.put(entry.getKey().toLowerCase(), entry.getValue()); 1327 } else 1328 descriptor = new HashMap<String, Object>(startMap); 1329 1330 fields.put("descriptor", descriptor); 1331 out.writeFields(); 1332 } 1333 1334 }