001/* 002 * Copyright 2011-2016 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.subAttributes != null) 355 { 356 final AttributeDescriptor subAttributesDescriptor = 357 allowNesting ? attributeDescriptor : 358 attributeDescriptor.getSubAttribute("subAttributes"); 359 final SCIMAttributeValue[] subAttributeValues = 360 new SCIMAttributeValue[value.subAttributes.size()]; 361 int i = 0; 362 for(AttributeDescriptor subAttribute : value.subAttributes.values()) 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 Map<String, AttributeDescriptor> allSubAttributes = 511 CoreSchema.addNormativeSubAttributes(this, subAttributes); 512 return allSubAttributes == null ? null : allSubAttributes.values(); 513 } 514 515 /** 516 * Retrieves the set of descriptors for subordinate attributes of a 517 * complex attribute. This method does not return normative 518 * sub-attributes that were not declared in the schema. 519 * 520 * @return The set of descriptors for subordinate attributes of a complex 521 * attribute, or {@code null} if the attribute is not a complex 522 * attribute. 523 */ 524 public Collection<AttributeDescriptor> getDeclaredSubAttributes() 525 { 526 return subAttributes == null ? null : subAttributes.values(); 527 } 528 529 /** 530 * Retrieves the attribute descriptor for a specified subordinate attribute 531 * of a complex attribute. 532 * 533 * @param externalName The external name of the subordinate attribute for 534 * which a descriptor is required. 535 * @return The attribute descriptor for the specified subordinate attribute. 536 * @throws InvalidResourceException if there is no such attribute. 537 */ 538 public AttributeDescriptor getSubAttribute(final String externalName) 539 throws InvalidResourceException 540 { 541 // TODO: Should we have a strict and non strict mode? 542 Map<String, AttributeDescriptor> allSubAttributes = 543 CoreSchema.addNormativeSubAttributes(this, subAttributes); 544 AttributeDescriptor subAttribute = 545 allSubAttributes == null ? null : 546 allSubAttributes.get(toLowerCase(externalName)); 547 if(subAttribute == null) 548 { 549 throw new InvalidResourceException("Sub-attribute " + externalName + 550 " is not defined for attribute " + schema + 551 SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE + name); 552 } 553 return subAttribute; 554 } 555 556 /** 557 * Retrieves a list of canonical type values. 558 * 559 * @return A list of canonical type values or <code>null</code> if the 560 * attribute is not a multi-valued attribute or if they are not 561 * specified. 562 */ 563 public Collection<Entry<String>> getCanonicalValues() 564 { 565 return canonicalValues; 566 } 567 568 /** 569 * Retrieve the data type for this attribute. 570 * 571 * @return The data type for this attribute, or {@code null} if the attribute 572 * is not a simple attribute. 573 */ 574 public DataType getDataType() 575 { 576 return dataType; 577 } 578 579 /** 580 * Retrieves this attribute's human readable description. 581 * 582 * @return This attribute's human redable description. 583 */ 584 public String getDescription() 585 { 586 return description; 587 } 588 589 /** 590 * Specifies if this attribute is mutable. 591 * 592 * @return <code>false</code> if this attribute is mutable or 593 * <code>true</code> otherwise. 594 */ 595 public boolean isReadOnly() 596 { 597 return readOnly; 598 } 599 600 /** 601 * Specifies if this attribute is required. 602 * 603 * @return <code>true</code> if this attribute is required for 604 * <code>false</code> otherwise. 605 */ 606 public boolean isRequired() 607 { 608 return required; 609 } 610 611 /** 612 * Specifies if the string attribute is case sensitive. 613 * 614 * @return <code>true</code> if this attribute is case sensitive or 615 * <code>false</code> otherwise. 616 */ 617 public boolean isCaseExact() 618 { 619 return caseExact; 620 } 621 622 @Override 623 public String toString() 624 { 625 return "AttributeDescriptor{" + 626 "schema='" + getSchema() + '\'' + 627 ", name='" + getName() + '\'' + 628 ", description='" + getDescription() + '\'' + 629 ", multiValued=" + isMultiValued() + 630 ", dataType=" + getDataType() + 631 ", isRequired=" + isRequired() + 632 ", isReadOnly=" + isReadOnly() + 633 ", isCaseExact=" + isCaseExact() + 634 '}'; 635 } 636 637 /** 638 * {@inheritDoc} 639 */ 640 @Override 641 public int hashCode() 642 { 643 int hashCode = 0; 644 645 hashCode += toLowerCase(schema).hashCode(); 646 hashCode += toLowerCase(name).hashCode(); 647 648 return hashCode; 649 } 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 public boolean equals(final Object obj) 656 { 657 if (this == obj) 658 { 659 return true; 660 } 661 662 if (!(obj instanceof AttributeDescriptor)) 663 { 664 return false; 665 } 666 667 final AttributeDescriptor that = (AttributeDescriptor)obj; 668 if (this.schema == null && that.schema == null) 669 { 670 return this.name.equalsIgnoreCase(that.name); 671 } 672 else 673 { 674 return this.schema != null && that.schema != null && 675 this.schema.equalsIgnoreCase(that.schema) && 676 this.name.equalsIgnoreCase(that.name); 677 } 678 } 679 680 /** 681 * Create a new sub-attribute descriptor with the provided information. 682 * 683 * @param name The attribute's name. 684 * @param dataType The attribute's data type. 685 * @param description The attribute's human readable description. 686 * @param schema The attribute's associated schema. 687 * @param readOnly Whether the attribute is mutable. 688 * @param required Whether the attribute is required. 689 * @param caseExact Whether the string attribute is case sensitive. 690 * @param canonicalValues A collection of canonical values. 691 * @return A new singular sub-attribute descriptor with the 692 * provided information. 693 */ 694 public static AttributeDescriptor createSubAttribute( 695 final String name, final DataType dataType, final String description, 696 final String schema, final boolean readOnly, final boolean required, 697 final boolean caseExact, final String... canonicalValues) 698 { 699 final Collection<Entry<String>> values; 700 if(canonicalValues != null && canonicalValues.length > 0) 701 { 702 values = new ArrayList<Entry<String>>(canonicalValues.length); 703 for(String canonicalValue : canonicalValues) 704 { 705 values.add(new Entry<String>(canonicalValue, null, false)); 706 } 707 } 708 else 709 { 710 values = null; 711 } 712 return new AttributeDescriptor(name, dataType, false, null, 713 description, schema, readOnly, required, caseExact, values, null); 714 } 715 716 /** 717 * Create a new singular attribute descriptor with the provided information. 718 * 719 * @param name The attribute's name. 720 * @param dataType The attribute's data type. 721 * @param description The attribute's human readable description. 722 * @param schema The attribute's associated schema. 723 * @param readOnly Whether the attribute is mutable. 724 * @param required Whether the attribute is required. 725 * @param caseExact Whether the string attribute is case sensitive. 726 * @param subAttributes A list specifying the contained attributes. 727 * @return A new singular attribute descriptor 728 * with the provided information. 729 */ 730 public static AttributeDescriptor createAttribute( 731 final String name, final DataType dataType, final String description, 732 final String schema, final boolean readOnly, final boolean required, 733 final boolean caseExact, final AttributeDescriptor... subAttributes) 734 { 735 if(subAttributes != null) 736 { 737 for(AttributeDescriptor subAttribute : subAttributes) 738 { 739 if(subAttribute.getDataType() == DataType.COMPLEX) 740 { 741 throw new IllegalArgumentException("Complex sub-attributes are not " + 742 "allowed"); 743 } 744 } 745 } 746 return newAttribute(name, null, dataType, description, schema, false, 747 readOnly, required, caseExact, subAttributes); 748 } 749 750 /** 751 * Create a new multi-valued attribute descriptor with the provided 752 * information. The normative sub-attributes for multi-valued attributes 753 * (ie. type, primary, display, operation, value) will be added. 754 * 755 * @param name The attribute's name. 756 * @param multiValuedChildName The child XML element name for multi-valued 757 * attributes. 758 * @param description The attribute's human readable description. 759 * @param schema The attribute's associated schema. 760 * @param readOnly Whether the attribute is mutable. 761 * @param required Whether the attribute is required. 762 * @param caseExact Whether the string attribute is case sensitive. 763 * @param subAttributes A list specifying the contained attributes. 764 * @return A new multi-valued attribute descriptor 765 * with the provided information. 766 */ 767 public static AttributeDescriptor createMultiValuedAttribute( 768 final String name, final String multiValuedChildName, 769 final String description, final String schema, 770 final boolean readOnly, final boolean required, final boolean caseExact, 771 final AttributeDescriptor... subAttributes) 772 { 773 if(subAttributes != null) 774 { 775 for(AttributeDescriptor subAttribute : subAttributes) 776 { 777 if(subAttribute.getDataType() == DataType.COMPLEX) 778 { 779 throw new IllegalArgumentException("Complex sub-attributes are not " + 780 "allowed"); 781 } 782 } 783 } 784 return newAttribute(name, multiValuedChildName, DataType.COMPLEX, 785 description, schema, true, readOnly, required, caseExact, 786 subAttributes); 787 } 788 789 /** 790 * Create a new attribute descriptor with the provided information. 791 * 792 * @param name The attribute's name. 793 * @param multiValuedChildName The child XML element name for multi-valued 794 * attributes. 795 * @param dataType The attribute's data type. 796 * @param description The attribute's human readable description. 797 * @param schema The attribute's associated schema. 798 * @param multiValued Whether the attribute is multiValued. 799 * @param readOnly Whether the attribute is mutable. 800 * @param required Whether the attribute is required. 801 * @param caseExact Whether the string attribute is case sensitive. 802 * @param subAttributes A list specifying the contained attributes. 803 * @return A new attribute descriptor with the provided 804 * information. 805 */ 806 static AttributeDescriptor newAttribute( 807 final String name, final String multiValuedChildName, 808 final DataType dataType, final String description, final String schema, 809 final boolean multiValued, final boolean readOnly, final boolean required, 810 final boolean caseExact, final AttributeDescriptor... subAttributes) 811 { 812 Collection<AttributeDescriptor> subAttrs = null; 813 if (subAttributes != null) 814 { 815 subAttrs = Arrays.asList(subAttributes); 816 } 817 return new AttributeDescriptor(name, dataType, multiValued, 818 multiValuedChildName, description, schema, readOnly, required, 819 caseExact, null, subAttrs); 820 } 821}