001/* 002 * Copyright 2011-2013 UnboundID Corp. 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim.schema; 019 020import com.unboundid.scim.data.AttributeValueResolver; 021import com.unboundid.scim.data.Entry; 022import com.unboundid.scim.sdk.InvalidResourceException; 023import com.unboundid.scim.sdk.SCIMAttribute; 024import com.unboundid.scim.sdk.SCIMAttributeValue; 025import com.unboundid.scim.sdk.SCIMConstants; 026 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Collection; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033 034import static com.unboundid.scim.sdk.StaticUtils.toLowerCase; 035 036 037 038/** 039 * This class provides methods that describe the schema for a SCIM attribute. 040 * It may be used to help read and write SCIM attributes in their external XML 041 * and JSON representation, and to convert SCIM attributes to and from LDAP 042 * attributes. 043 */ 044public final class AttributeDescriptor { 045 046 /** 047 * Defines the set of well known SCIM supported datatypes. 048 */ 049 public static enum DataType { 050 /** 051 * String data type. 052 */ 053 STRING, 054 /** 055 * Boolean data type. 056 */ 057 BOOLEAN, 058 /** 059 * Date Time data type. 060 */ 061 DATETIME, 062 /** 063 * Decimal data type. 064 */ 065 DECIMAL, 066 /** 067 * Integer data type. 068 */ 069 INTEGER, 070 /** 071 * Binary data type. 072 */ 073 BINARY, 074 /** 075 * Complex data type. 076 */ 077 COMPLEX; 078 079 /** 080 * Parses a supplied data type into a SCIM defined data type. 081 * @param type The type to convert 082 * 083 * @return The DataType or null if not supported 084 */ 085 public static DataType parse(final String type) { 086 try { 087 return DataType.valueOf(type.toUpperCase()); 088 } catch (Exception e) { 089 return null; 090 } 091 } 092 093 /** 094 * {@inheritDoc} 095 */ 096 @Override 097 public String toString() { 098 return super.toString().toLowerCase(); 099 } 100 } 101 102 static final class SubAttributeDescriptorResolver 103 extends AttributeValueResolver<AttributeDescriptor> 104 { 105 private final String schema; 106 107 /** 108 * Construct a new sub attribute resolver. 109 * 110 * @param schema The schema of the parent attribute. 111 */ 112 SubAttributeDescriptorResolver(final String schema) { 113 this.schema = schema; 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override 120 public SCIMAttributeValue fromInstance( 121 final AttributeDescriptor attributeDescriptor, 122 final AttributeDescriptor value) throws InvalidResourceException { 123 124 final List<SCIMAttribute> attributes = new ArrayList<SCIMAttribute>(9); 125 126 attributes.add(SCIMAttribute.create( 127 attributeDescriptor.getSubAttribute("name"), 128 SCIMAttributeValue.createStringValue(value.getName()))); 129 130 attributes.add(SCIMAttribute.create( 131 attributeDescriptor.getSubAttribute("type"), 132 SCIMAttributeValue.createStringValue( 133 value.getDataType().toString()))); 134 135 136 attributes.add(SCIMAttribute.create( 137 attributeDescriptor.getSubAttribute("multiValued"), 138 SCIMAttributeValue.createBooleanValue(value.isMultiValued()))); 139 140 if (value.isMultiValued()) 141 { 142 attributes.add(SCIMAttribute.create( 143 attributeDescriptor.getSubAttribute("multiValuedAttributeChildName"), 144 SCIMAttributeValue.createStringValue( 145 value.getMultiValuedChildName()))); 146 } 147 148 attributes.add(SCIMAttribute.create( 149 attributeDescriptor.getSubAttribute("description"), 150 SCIMAttributeValue.createStringValue(value.getDescription()))); 151 152 attributes.add(SCIMAttribute.create( 153 attributeDescriptor.getSubAttribute("readOnly"), 154 SCIMAttributeValue.createBooleanValue(value.isReadOnly()))); 155 156 attributes.add(SCIMAttribute.create( 157 attributeDescriptor.getSubAttribute("required"), 158 SCIMAttributeValue.createBooleanValue(value.isRequired()))); 159 160 attributes.add(SCIMAttribute.create( 161 attributeDescriptor.getSubAttribute("caseExact"), 162 SCIMAttributeValue.createBooleanValue(value.isCaseExact()))); 163 164 if(value.getCanonicalValues() != null) 165 { 166 final AttributeDescriptor canonicalValuesAttributeDescriptor = 167 attributeDescriptor.getSubAttribute("canonicalValues"); 168 final SCIMAttributeValue[] canonicalValues = 169 new SCIMAttributeValue[value.getCanonicalValues().size()]; 170 int i = 0; 171 for(Entry<String> canonicalValue : value.getCanonicalValues()) 172 { 173 canonicalValues[i++] = Entry.STRINGS_RESOLVER.fromInstance( 174 canonicalValuesAttributeDescriptor, canonicalValue); 175 } 176 attributes.add(SCIMAttribute.create( 177 canonicalValuesAttributeDescriptor, canonicalValues)); 178 } 179 180 return SCIMAttributeValue.createComplexValue(attributes); 181 } 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public AttributeDescriptor toInstance(final SCIMAttributeValue value) { 188 189 DataType dataType = DataType.parse(value.getSubAttributeValue( 190 "type", AttributeValueResolver.STRING_RESOLVER)); 191 192 return new AttributeDescriptor( 193 194 value.getSubAttributeValue( 195 "name", AttributeValueResolver.STRING_RESOLVER), 196 197 dataType, 198 199 value.getSubAttributeValue( 200 "multiValued", AttributeValueResolver.BOOLEAN_RESOLVER), 201 202 value.getSubAttributeValue( 203 "multiValuedAttributeChildName", 204 AttributeValueResolver.STRING_RESOLVER), 205 206 value.getSubAttributeValue( 207 "description", AttributeValueResolver.STRING_RESOLVER), 208 209 schema, 210 211 value.getSubAttributeValue( 212 "readOnly", AttributeValueResolver.BOOLEAN_RESOLVER), 213 214 value.getSubAttributeValue( 215 "required", AttributeValueResolver.BOOLEAN_RESOLVER), 216 217 value.getSubAttributeValue( 218 "caseExact", AttributeValueResolver.BOOLEAN_RESOLVER), 219 220 value.getSubAttributeValues( 221 "canonicalValues", Entry.STRINGS_RESOLVER), 222 223 Arrays.asList(AttributeDescriptor.createSubAttribute( 224 "value", dataType, "The attribute's significant value", 225 SCIMConstants.SCHEMA_URI_CORE, false, true, false))); 226 } 227 } 228 229 /** 230 * The <code>AttributeValueResolver</code> that resolves SCIM attribute values 231 * to/from <code>AttributeDescriptor</code> instances. 232 */ 233 public static final AttributeValueResolver<AttributeDescriptor> 234 ATTRIBUTE_DESCRIPTOR_RESOLVER = new AttributeDescriptorResolver(false); 235 236 static final class AttributeDescriptorResolver extends 237 AttributeValueResolver<AttributeDescriptor> 238 { 239 private final boolean allowNesting; 240 241 /** 242 * Create a new AttributeDescriptorResolver. 243 * 244 * @param allowNesting <code>true</code> to allow nesting of complex 245 * types or <code>false</code> otherwise. 246 */ 247 AttributeDescriptorResolver(final boolean allowNesting) { 248 this.allowNesting = allowNesting; 249 } 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public AttributeDescriptor toInstance(final SCIMAttributeValue value) { 256 String schemaValue = value.getSubAttributeValue( 257 "schema", AttributeValueResolver.STRING_RESOLVER); 258 259 boolean multiValued = value.getSubAttributeValue( 260 "multiValued", AttributeValueResolver.BOOLEAN_RESOLVER); 261 262 String multiValuedChildName = null; 263 if (multiValued) 264 { 265 multiValuedChildName = value.getSubAttributeValue( 266 "multiValuedAttributeChildName", 267 AttributeValueResolver.STRING_RESOLVER); 268 } 269 270 return new AttributeDescriptor( 271 272 value.getSubAttributeValue( 273 "name", AttributeValueResolver.STRING_RESOLVER), 274 275 DataType.parse(value.getSubAttributeValue( 276 "type", AttributeValueResolver.STRING_RESOLVER)), 277 278 multiValued, multiValuedChildName, 279 280 value.getSubAttributeValue( 281 "description", AttributeValueResolver.STRING_RESOLVER), 282 283 schemaValue, 284 285 value.getSubAttributeValue( 286 "readOnly", AttributeValueResolver.BOOLEAN_RESOLVER), 287 288 value.getSubAttributeValue( 289 "required", AttributeValueResolver.BOOLEAN_RESOLVER), 290 291 value.getSubAttributeValue( 292 "caseExact", AttributeValueResolver.BOOLEAN_RESOLVER), 293 294 null, 295 296 value.getSubAttributeValues( 297 allowNesting ? "attributes" : "subAttributes", 298 allowNesting ? this : 299 new SubAttributeDescriptorResolver(schemaValue))); 300 } 301 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public SCIMAttributeValue fromInstance( 307 final AttributeDescriptor attributeDescriptor, 308 final AttributeDescriptor value) throws InvalidResourceException { 309 310 final List<SCIMAttribute> attributes = 311 new ArrayList<SCIMAttribute>(10); 312 313 attributes.add(SCIMAttribute.create( 314 attributeDescriptor.getSubAttribute("name"), 315 SCIMAttributeValue.createStringValue(value.getName()))); 316 317 attributes.add(SCIMAttribute.create( 318 attributeDescriptor.getSubAttribute("type"), 319 SCIMAttributeValue.createStringValue( 320 value.getDataType().toString()))); 321 322 attributes.add(SCIMAttribute.create( 323 attributeDescriptor.getSubAttribute("multiValued"), 324 SCIMAttributeValue.createBooleanValue(value.isMultiValued()))); 325 326 if(value.isMultiValued()) 327 { 328 attributes.add(SCIMAttribute.create( 329 attributeDescriptor.getSubAttribute("multiValuedAttributeChildName"), 330 SCIMAttributeValue.createStringValue( 331 value.getMultiValuedChildName()))); 332 } 333 334 attributes.add(SCIMAttribute.create( 335 attributeDescriptor.getSubAttribute("description"), 336 SCIMAttributeValue.createStringValue(value.getDescription()))); 337 338 attributes.add(SCIMAttribute.create( 339 attributeDescriptor.getSubAttribute("schema"), 340 SCIMAttributeValue.createStringValue(value.getSchema()))); 341 342 attributes.add(SCIMAttribute.create( 343 attributeDescriptor.getSubAttribute("readOnly"), 344 SCIMAttributeValue.createBooleanValue(value.isReadOnly()))); 345 346 attributes.add(SCIMAttribute.create( 347 attributeDescriptor.getSubAttribute("required"), 348 SCIMAttributeValue.createBooleanValue(value.isRequired()))); 349 350 attributes.add(SCIMAttribute.create( 351 attributeDescriptor.getSubAttribute("caseExact"), 352 SCIMAttributeValue.createBooleanValue(value.isCaseExact()))); 353 354 if(value.getSubAttributes() != null) 355 { 356 final AttributeDescriptor subAttributesDescriptor = 357 allowNesting ? attributeDescriptor : 358 attributeDescriptor.getSubAttribute("subAttributes"); 359 final SCIMAttributeValue[] subAttributeValues = 360 new SCIMAttributeValue[value.getSubAttributes().size()]; 361 int i = 0; 362 for(AttributeDescriptor subAttribute : value.getSubAttributes()) 363 { 364 subAttributeValues[i++] = (allowNesting ? this : 365 new SubAttributeDescriptorResolver(value.getSchema())). 366 fromInstance(subAttributesDescriptor, subAttribute); 367 } 368 attributes.add(SCIMAttribute.create( 369 subAttributesDescriptor, subAttributeValues)); 370 } 371 372 return SCIMAttributeValue.createComplexValue(attributes); 373 } 374 } 375 376 private final String schema; 377 378 private final String name; 379 380 private final String description; 381 382 private final boolean readOnly; 383 384 private final boolean required; 385 386 private final boolean multiValued; 387 388 private final String multiValuedChildName; 389 390 private final boolean caseExact; 391 392 private final DataType dataType; 393 394 private final Map<String, AttributeDescriptor> subAttributes; 395 396 private final Collection<Entry<String>> canonicalValues; 397 398 /** 399 * Construct a new AttributeDescriptor instance with the provided info. 400 * 401 * @param name The attribute's name. 402 * @param dataType The attribute's data type. 403 * @param multiValued Whether the attribute is multiValued. 404 * @param multiValuedChildName The child XML element name for multi-valued 405 * attributes. 406 * @param description The attribute's human readable description. 407 * @param schema The attribute's associated schema. 408 * @param readOnly Whether the attribute is mutable. 409 * @param required Whether the attribute is required. 410 * @param caseExact Whether the string attribute is case sensitive. 411 * @param canonicalValues A list of canonical type values. 412 * @param subAttributes A list specifying the contained attributes. 413 */ 414 private AttributeDescriptor(final String name, final DataType dataType, 415 final boolean multiValued, 416 final String multiValuedChildName, 417 final String description, final String schema, 418 final boolean readOnly, final boolean required, 419 final boolean caseExact, 420 final Collection<Entry<String>> canonicalValues, 421 final Collection<AttributeDescriptor> subAttributes) 422 { 423 this.name = name; 424 this.dataType = dataType; 425 this.multiValued = multiValued; 426 this.multiValuedChildName = multiValuedChildName; 427 this.description = description; 428 this.schema = schema; 429 this.readOnly = readOnly; 430 this.required = required; 431 this.caseExact = caseExact; 432 433 if(canonicalValues != null && !canonicalValues.isEmpty()) 434 { 435 this.canonicalValues = canonicalValues; 436 } 437 else 438 { 439 this.canonicalValues = null; 440 } 441 442 if(subAttributes != null && !subAttributes.isEmpty()) 443 { 444 this.subAttributes = 445 new LinkedHashMap<String, AttributeDescriptor>(subAttributes.size()); 446 for(AttributeDescriptor attributeDescriptor : subAttributes) 447 { 448 this.subAttributes.put(toLowerCase( 449 attributeDescriptor.getName()), 450 attributeDescriptor); 451 } 452 } 453 else 454 { 455 this.subAttributes = null; 456 } 457 } 458 459 /** 460 * The URI for the schema that defines the SCIM attribute. 461 * 462 * @return The URI for the schema that defines the SCIM attribute. 463 * It is never {@code null}. 464 */ 465 public String getSchema() { 466 return schema; 467 } 468 469 /** 470 * The attribute name to be used in any external representation of the SCIM 471 * attribute. 472 * 473 * @return The attribute name to be used in any external representation of 474 * the SCIM attribute. It is never {@code null}. 475 */ 476 public String getName() { 477 return name; 478 } 479 480 /** 481 * Indicates whether the attribute is a multi-valued attribute. 482 * 483 * @return {@code true} if the attribute is multi-valued. 484 */ 485 public boolean isMultiValued() { 486 return multiValued; 487 } 488 489 /** 490 * The child XML element name for multi-valued attributes; e.g., the 491 * 'emails' attribute value is 'email', 'phoneNumbers', is 'phoneNumber'. 492 * 493 * @return The child XML element name or {@code null} if this attribute 494 * is not multi-valued. 495 */ 496 public String getMultiValuedChildName() { 497 return multiValuedChildName; 498 } 499 500 /** 501 * Retrieves the set of descriptors for subordinate attributes of a complex 502 * attribute. 503 * 504 * @return The set of descriptors for subordinate attributes of a complex 505 * attribute, or {@code null} if the attribute is not a complex 506 * attribute. 507 */ 508 public Collection<AttributeDescriptor> getSubAttributes() 509 { 510 return subAttributes == null ? null : subAttributes.values(); 511 } 512 513 /** 514 * Retrieves the attribute descriptor for a specified subordinate attribute 515 * of a complex attribute. 516 * 517 * @param externalName The external name of the subordinate attribute for 518 * which a descriptor is required. 519 * @return The attribute descriptor for the specified subordinate attribute. 520 * @throws InvalidResourceException if there is no such attribute. 521 */ 522 public AttributeDescriptor getSubAttribute(final String externalName) 523 throws InvalidResourceException 524 { 525 // TODO: Should we have a strict and non strict mode? 526 AttributeDescriptor subAttribute = 527 subAttributes == null ? null : 528 subAttributes.get(toLowerCase(externalName)); 529 if(subAttribute == null) 530 { 531 throw new InvalidResourceException("Sub-attribute " + externalName + 532 " is not defined for attribute " + schema + 533 SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE + name); 534 } 535 return subAttribute; 536 } 537 538 /** 539 * Retrieves a list of canonical type values. 540 * 541 * @return A list of canonical type values or <code>null</code> if the 542 * attribute is not a multi-valued attribute or if they are not 543 * specified. 544 */ 545 public Collection<Entry<String>> getCanonicalValues() 546 { 547 return canonicalValues; 548 } 549 550 /** 551 * Retrieve the data type for this attribute. 552 * 553 * @return The data type for this attribute, or {@code null} if the attribute 554 * is not a simple attribute. 555 */ 556 public DataType getDataType() 557 { 558 return dataType; 559 } 560 561 /** 562 * Retrieves this attribute's human readable description. 563 * 564 * @return This attribute's human redable description. 565 */ 566 public String getDescription() 567 { 568 return description; 569 } 570 571 /** 572 * Specifies if this attribute is mutable. 573 * 574 * @return <code>false</code> if this attribute is mutable or 575 * <code>true</code> otherwise. 576 */ 577 public boolean isReadOnly() 578 { 579 return readOnly; 580 } 581 582 /** 583 * Specifies if this attribute is required. 584 * 585 * @return <code>true</code> if this attribute is required for 586 * <code>false</code> otherwise. 587 */ 588 public boolean isRequired() 589 { 590 return required; 591 } 592 593 /** 594 * Specifies if the string attribute is case sensitive. 595 * 596 * @return <code>true</code> if this attribute is case sensitive or 597 * <code>false</code> otherwise. 598 */ 599 public boolean isCaseExact() 600 { 601 return caseExact; 602 } 603 604 @Override 605 public String toString() 606 { 607 return "AttributeDescriptor{" + 608 "schema='" + getSchema() + '\'' + 609 ", name='" + getName() + '\'' + 610 ", description='" + getDescription() + '\'' + 611 ", multiValued=" + isMultiValued() + 612 ", dataType=" + getDataType() + 613 ", isRequired=" + isRequired() + 614 ", isReadOnly=" + isReadOnly() + 615 ", isCaseExact=" + isCaseExact() + 616 '}'; 617 } 618 619 /** 620 * {@inheritDoc} 621 */ 622 @Override 623 public int hashCode() 624 { 625 int hashCode = 0; 626 627 hashCode += toLowerCase(schema).hashCode(); 628 hashCode += toLowerCase(name).hashCode(); 629 630 return hashCode; 631 } 632 633 /** 634 * {@inheritDoc} 635 */ 636 @Override 637 public boolean equals(final Object obj) 638 { 639 if (this == obj) 640 { 641 return true; 642 } 643 644 if (!(obj instanceof AttributeDescriptor)) 645 { 646 return false; 647 } 648 649 final AttributeDescriptor that = (AttributeDescriptor)obj; 650 if (this.schema == null && that.schema == null) 651 { 652 return this.name.equalsIgnoreCase(that.name); 653 } 654 else 655 { 656 return this.schema != null && that.schema != null && 657 this.schema.equalsIgnoreCase(that.schema) && 658 this.name.equalsIgnoreCase(that.name); 659 } 660 } 661 662 /** 663 * Create a new sub-attribute descriptor with the provided information. 664 * 665 * @param name The attribute's name. 666 * @param dataType The attribute's data type. 667 * @param description The attribute's human readable description. 668 * @param schema The attribute's associated schema. 669 * @param readOnly Whether the attribute is mutable. 670 * @param required Whether the attribute is required. 671 * @param caseExact Whether the string attribute is case sensitive. 672 * @param canonicalValues A collection of canonical values. 673 * @return A new singular sub-attribute descriptor with the 674 * provided information. 675 */ 676 public static AttributeDescriptor createSubAttribute( 677 final String name, final DataType dataType, final String description, 678 final String schema, final boolean readOnly, final boolean required, 679 final boolean caseExact, final String... canonicalValues) 680 { 681 final Collection<Entry<String>> values; 682 if(canonicalValues != null && canonicalValues.length > 0) 683 { 684 values = new ArrayList<Entry<String>>(canonicalValues.length); 685 for(String canonicalValue : canonicalValues) 686 { 687 values.add(new Entry<String>(canonicalValue, null, false)); 688 } 689 } 690 else 691 { 692 values = null; 693 } 694 return new AttributeDescriptor(name, dataType, false, null, 695 description, schema, readOnly, required, caseExact, values, null); 696 } 697 698 /** 699 * Create a new singular attribute descriptor with the provided information. 700 * 701 * @param name The attribute's name. 702 * @param dataType The attribute's data type. 703 * @param description The attribute's human readable description. 704 * @param schema The attribute's associated schema. 705 * @param readOnly Whether the attribute is mutable. 706 * @param required Whether the attribute is required. 707 * @param caseExact Whether the string attribute is case sensitive. 708 * @param subAttributes A list specifying the contained attributes. 709 * @return A new singular attribute descriptor 710 * with the provided information. 711 */ 712 public static AttributeDescriptor createAttribute( 713 final String name, final DataType dataType, final String description, 714 final String schema, final boolean readOnly, final boolean required, 715 final boolean caseExact, final AttributeDescriptor... subAttributes) 716 { 717 return newAttribute(name, null, dataType, description, schema, readOnly, 718 required, caseExact, subAttributes); 719 } 720 721 /** 722 * Create a new multi-valued attribute descriptor with the provided 723 * information. The normative sub-attributes for multi-valued attributes 724 * (ie. type, primary, display, operation, value) will be added. 725 * 726 * @param name The attribute's name. 727 * @param multiValuedChildName The child XML element name for multi-valued 728 * attributes. 729 * @param dataType The attribute's data type. 730 * @param description The attribute's human readable description. 731 * @param schema The attribute's associated schema. 732 * @param readOnly Whether the attribute is mutable. 733 * @param required Whether the attribute is required. 734 * @param caseExact Whether the string attribute is case sensitive. 735 * @param canonicalValues The list of canonical values for the type attribute. 736 * @param subAttributes A list specifying the contained attributes. 737 * @return A new multi-valued attribute descriptor 738 * with the provided information. 739 */ 740 public static AttributeDescriptor createMultiValuedAttribute( 741 final String name, final String multiValuedChildName, 742 final DataType dataType, final String description, final String schema, 743 final boolean readOnly, final boolean required, final boolean caseExact, 744 final String[] canonicalValues, 745 final AttributeDescriptor... subAttributes) 746 { 747 return newAttribute(name, multiValuedChildName, dataType, 748 description, schema, readOnly, required, caseExact, 749 CoreSchema.addCommonMultiValuedSubAttributes( 750 schema, dataType, canonicalValues, subAttributes)); 751 } 752 753 /** 754 * Create a new attribute descriptor with the provided information. 755 * 756 * @param name The attribute's name. 757 * @param multiValuedChildName The child XML element name for multi-valued 758 * attributes. 759 * @param dataType The attribute's data type. 760 * @param description The attribute's human readable description. 761 * @param schema The attribute's associated schema. 762 * @param readOnly Whether the attribute is mutable. 763 * @param required Whether the attribute is required. 764 * @param caseExact Whether the string attribute is case sensitive. 765 * @param subAttributes A list specifying the contained attributes. 766 * @return A new attribute descriptor with the provided 767 * information. 768 */ 769 static AttributeDescriptor newAttribute( 770 final String name, final String multiValuedChildName, 771 final DataType dataType, final String description, final String schema, 772 final boolean readOnly, final boolean required, final boolean caseExact, 773 final AttributeDescriptor... subAttributes) 774 { 775 Collection<AttributeDescriptor> subAttrs = null; 776 if (subAttributes != null) 777 { 778 subAttrs = Arrays.asList(subAttributes); 779 } 780 return new AttributeDescriptor(name, dataType, multiValuedChildName != null, 781 multiValuedChildName, description, schema, readOnly, required, 782 caseExact, null, subAttrs); 783 } 784}