001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.schema; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Map; 028import java.util.LinkedHashMap; 029 030import com.unboundid.ldap.sdk.LDAPException; 031import com.unboundid.ldap.sdk.ResultCode; 032import com.unboundid.util.NotMutable; 033import com.unboundid.util.ThreadSafety; 034import com.unboundid.util.ThreadSafetyLevel; 035 036import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 037import static com.unboundid.util.StaticUtils.*; 038import static com.unboundid.util.Validator.*; 039 040 041 042/** 043 * This class provides a data structure that describes an LDAP DIT content rule 044 * schema element. 045 */ 046@NotMutable() 047@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 048public final class DITContentRuleDefinition 049 extends SchemaElement 050{ 051 /** 052 * The serial version UID for this serializable class. 053 */ 054 private static final long serialVersionUID = 3224440505307817586L; 055 056 057 058 // Indicates whether this DIT content rule is declared obsolete. 059 private final boolean isObsolete; 060 061 // The set of extensions for this DIT content rule. 062 private final Map<String,String[]> extensions; 063 064 // The description for this DIT content rule. 065 private final String description; 066 067 // The string representation of this DIT content rule. 068 private final String ditContentRuleString; 069 070 // The OID of the structural object class with which this DIT content rule is 071 // associated. 072 private final String oid; 073 074 // The names/OIDs of the allowed auxiliary classes. 075 private final String[] auxiliaryClasses; 076 077 // The set of names for this DIT content rule. 078 private final String[] names; 079 080 // The names/OIDs of the optional attributes. 081 private final String[] optionalAttributes; 082 083 // The names/OIDs of the prohibited attributes. 084 private final String[] prohibitedAttributes; 085 086 // The names/OIDs of the required attributes. 087 private final String[] requiredAttributes; 088 089 090 091 /** 092 * Creates a new DIT content rule from the provided string representation. 093 * 094 * @param s The string representation of the DIT content rule to create, 095 * using the syntax described in RFC 4512 section 4.1.6. It must 096 * not be {@code null}. 097 * 098 * @throws LDAPException If the provided string cannot be decoded as a DIT 099 * content rule definition. 100 */ 101 public DITContentRuleDefinition(final String s) 102 throws LDAPException 103 { 104 ensureNotNull(s); 105 106 ditContentRuleString = s.trim(); 107 108 // The first character must be an opening parenthesis. 109 final int length = ditContentRuleString.length(); 110 if (length == 0) 111 { 112 throw new LDAPException(ResultCode.DECODING_ERROR, 113 ERR_DCR_DECODE_EMPTY.get()); 114 } 115 else if (ditContentRuleString.charAt(0) != '(') 116 { 117 throw new LDAPException(ResultCode.DECODING_ERROR, 118 ERR_DCR_DECODE_NO_OPENING_PAREN.get( 119 ditContentRuleString)); 120 } 121 122 123 // Skip over any spaces until we reach the start of the OID, then read the 124 // OID until we find the next space. 125 int pos = skipSpaces(ditContentRuleString, 1, length); 126 127 StringBuilder buffer = new StringBuilder(); 128 pos = readOID(ditContentRuleString, pos, length, buffer); 129 oid = buffer.toString(); 130 131 132 // Technically, DIT content elements are supposed to appear in a specific 133 // order, but we'll be lenient and allow remaining elements to come in any 134 // order. 135 final ArrayList<String> nameList = new ArrayList<String>(1); 136 final ArrayList<String> reqAttrs = new ArrayList<String>(); 137 final ArrayList<String> optAttrs = new ArrayList<String>(); 138 final ArrayList<String> notAttrs = new ArrayList<String>(); 139 final ArrayList<String> auxOCs = new ArrayList<String>(); 140 final Map<String,String[]> exts = new LinkedHashMap<String,String[]>(); 141 Boolean obsolete = null; 142 String descr = null; 143 144 while (true) 145 { 146 // Skip over any spaces until we find the next element. 147 pos = skipSpaces(ditContentRuleString, pos, length); 148 149 // Read until we find the next space or the end of the string. Use that 150 // token to figure out what to do next. 151 final int tokenStartPos = pos; 152 while ((pos < length) && (ditContentRuleString.charAt(pos) != ' ')) 153 { 154 pos++; 155 } 156 157 // It's possible that the token could be smashed right up against the 158 // closing parenthesis. If that's the case, then extract just the token 159 // and handle the closing parenthesis the next time through. 160 String token = ditContentRuleString.substring(tokenStartPos, pos); 161 if ((token.length() > 1) && (token.endsWith(")"))) 162 { 163 token = token.substring(0, token.length() - 1); 164 pos--; 165 } 166 167 final String lowerToken = toLowerCase(token); 168 if (lowerToken.equals(")")) 169 { 170 // This indicates that we're at the end of the value. There should not 171 // be any more closing characters. 172 if (pos < length) 173 { 174 throw new LDAPException(ResultCode.DECODING_ERROR, 175 ERR_DCR_DECODE_CLOSE_NOT_AT_END.get( 176 ditContentRuleString)); 177 } 178 break; 179 } 180 else if (lowerToken.equals("name")) 181 { 182 if (nameList.isEmpty()) 183 { 184 pos = skipSpaces(ditContentRuleString, pos, length); 185 pos = readQDStrings(ditContentRuleString, pos, length, nameList); 186 } 187 else 188 { 189 throw new LDAPException(ResultCode.DECODING_ERROR, 190 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 191 ditContentRuleString, "NAME")); 192 } 193 } 194 else if (lowerToken.equals("desc")) 195 { 196 if (descr == null) 197 { 198 pos = skipSpaces(ditContentRuleString, pos, length); 199 200 buffer = new StringBuilder(); 201 pos = readQDString(ditContentRuleString, pos, length, buffer); 202 descr = buffer.toString(); 203 } 204 else 205 { 206 throw new LDAPException(ResultCode.DECODING_ERROR, 207 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 208 ditContentRuleString, "DESC")); 209 } 210 } 211 else if (lowerToken.equals("obsolete")) 212 { 213 if (obsolete == null) 214 { 215 obsolete = true; 216 } 217 else 218 { 219 throw new LDAPException(ResultCode.DECODING_ERROR, 220 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 221 ditContentRuleString, "OBSOLETE")); 222 } 223 } 224 else if (lowerToken.equals("aux")) 225 { 226 if (auxOCs.isEmpty()) 227 { 228 pos = skipSpaces(ditContentRuleString, pos, length); 229 pos = readOIDs(ditContentRuleString, pos, length, auxOCs); 230 } 231 else 232 { 233 throw new LDAPException(ResultCode.DECODING_ERROR, 234 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 235 ditContentRuleString, "AUX")); 236 } 237 } 238 else if (lowerToken.equals("must")) 239 { 240 if (reqAttrs.isEmpty()) 241 { 242 pos = skipSpaces(ditContentRuleString, pos, length); 243 pos = readOIDs(ditContentRuleString, pos, length, reqAttrs); 244 } 245 else 246 { 247 throw new LDAPException(ResultCode.DECODING_ERROR, 248 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 249 ditContentRuleString, "MUST")); 250 } 251 } 252 else if (lowerToken.equals("may")) 253 { 254 if (optAttrs.isEmpty()) 255 { 256 pos = skipSpaces(ditContentRuleString, pos, length); 257 pos = readOIDs(ditContentRuleString, pos, length, optAttrs); 258 } 259 else 260 { 261 throw new LDAPException(ResultCode.DECODING_ERROR, 262 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 263 ditContentRuleString, "MAY")); 264 } 265 } 266 else if (lowerToken.equals("not")) 267 { 268 if (notAttrs.isEmpty()) 269 { 270 pos = skipSpaces(ditContentRuleString, pos, length); 271 pos = readOIDs(ditContentRuleString, pos, length, notAttrs); 272 } 273 else 274 { 275 throw new LDAPException(ResultCode.DECODING_ERROR, 276 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get( 277 ditContentRuleString, "NOT")); 278 } 279 } 280 else if (lowerToken.startsWith("x-")) 281 { 282 pos = skipSpaces(ditContentRuleString, pos, length); 283 284 final ArrayList<String> valueList = new ArrayList<String>(); 285 pos = readQDStrings(ditContentRuleString, pos, length, valueList); 286 287 final String[] values = new String[valueList.size()]; 288 valueList.toArray(values); 289 290 if (exts.containsKey(token)) 291 { 292 throw new LDAPException(ResultCode.DECODING_ERROR, 293 ERR_DCR_DECODE_DUP_EXT.get( 294 ditContentRuleString, token)); 295 } 296 297 exts.put(token, values); 298 } 299 else 300 { 301 throw new LDAPException(ResultCode.DECODING_ERROR, 302 ERR_DCR_DECODE_DUP_EXT.get( 303 ditContentRuleString, token)); 304 } 305 } 306 307 description = descr; 308 309 names = new String[nameList.size()]; 310 nameList.toArray(names); 311 312 auxiliaryClasses = new String[auxOCs.size()]; 313 auxOCs.toArray(auxiliaryClasses); 314 315 requiredAttributes = new String[reqAttrs.size()]; 316 reqAttrs.toArray(requiredAttributes); 317 318 optionalAttributes = new String[optAttrs.size()]; 319 optAttrs.toArray(optionalAttributes); 320 321 prohibitedAttributes = new String[notAttrs.size()]; 322 notAttrs.toArray(prohibitedAttributes); 323 324 isObsolete = (obsolete != null); 325 326 extensions = Collections.unmodifiableMap(exts); 327 } 328 329 330 331 /** 332 * Creates a new DIT content rule with the provided information. 333 * 334 * @param oid The OID for the structural object class with 335 * which this DIT content rule is associated. 336 * It must not be {@code null}. 337 * @param names The set of names for this DIT content rule. 338 * It may be {@code null} or empty if the DIT 339 * content rule should only be referenced by 340 * OID. 341 * @param description The description for this DIT content rule. 342 * It may be {@code null} if there is no 343 * description. 344 * @param isObsolete Indicates whether this DIT content rule is 345 * declared obsolete. 346 * @param auxiliaryClasses The names/OIDs of the auxiliary object 347 * classes that may be present in entries 348 * containing this DIT content rule. 349 * @param requiredAttributes The names/OIDs of the attributes which must 350 * be present in entries containing this DIT 351 * content rule. 352 * @param optionalAttributes The names/OIDs of the attributes which may be 353 * present in entries containing this DIT 354 * content rule. 355 * @param prohibitedAttributes The names/OIDs of the attributes which may 356 * not be present in entries containing this DIT 357 * content rule. 358 * @param extensions The set of extensions for this DIT content 359 * rule. It may be {@code null} or empty if 360 * there should not be any extensions. 361 */ 362 public DITContentRuleDefinition(final String oid, final String[] names, 363 final String description, 364 final boolean isObsolete, 365 final String[] auxiliaryClasses, 366 final String[] requiredAttributes, 367 final String[] optionalAttributes, 368 final String[] prohibitedAttributes, 369 final Map<String,String[]> extensions) 370 { 371 ensureNotNull(oid); 372 373 this.oid = oid; 374 this.isObsolete = isObsolete; 375 this.description = description; 376 377 if (names == null) 378 { 379 this.names = NO_STRINGS; 380 } 381 else 382 { 383 this.names = names; 384 } 385 386 if (auxiliaryClasses == null) 387 { 388 this.auxiliaryClasses = NO_STRINGS; 389 } 390 else 391 { 392 this.auxiliaryClasses = auxiliaryClasses; 393 } 394 395 if (requiredAttributes == null) 396 { 397 this.requiredAttributes = NO_STRINGS; 398 } 399 else 400 { 401 this.requiredAttributes = requiredAttributes; 402 } 403 404 if (optionalAttributes == null) 405 { 406 this.optionalAttributes = NO_STRINGS; 407 } 408 else 409 { 410 this.optionalAttributes = optionalAttributes; 411 } 412 413 if (prohibitedAttributes == null) 414 { 415 this.prohibitedAttributes = NO_STRINGS; 416 } 417 else 418 { 419 this.prohibitedAttributes = prohibitedAttributes; 420 } 421 422 if (extensions == null) 423 { 424 this.extensions = Collections.emptyMap(); 425 } 426 else 427 { 428 this.extensions = Collections.unmodifiableMap(extensions); 429 } 430 431 final StringBuilder buffer = new StringBuilder(); 432 createDefinitionString(buffer); 433 ditContentRuleString = buffer.toString(); 434 } 435 436 437 438 /** 439 * Constructs a string representation of this DIT content rule definition in 440 * the provided buffer. 441 * 442 * @param buffer The buffer in which to construct a string representation of 443 * this DIT content rule definition. 444 */ 445 private void createDefinitionString(final StringBuilder buffer) 446 { 447 buffer.append("( "); 448 buffer.append(oid); 449 450 if (names.length == 1) 451 { 452 buffer.append(" NAME '"); 453 buffer.append(names[0]); 454 buffer.append('\''); 455 } 456 else if (names.length > 1) 457 { 458 buffer.append(" NAME ("); 459 for (final String name : names) 460 { 461 buffer.append(" '"); 462 buffer.append(name); 463 buffer.append('\''); 464 } 465 buffer.append(" )"); 466 } 467 468 if (description != null) 469 { 470 buffer.append(" DESC '"); 471 encodeValue(description, buffer); 472 buffer.append('\''); 473 } 474 475 if (isObsolete) 476 { 477 buffer.append(" OBSOLETE"); 478 } 479 480 if (auxiliaryClasses.length == 1) 481 { 482 buffer.append(" AUX "); 483 buffer.append(auxiliaryClasses[0]); 484 } 485 else if (auxiliaryClasses.length > 1) 486 { 487 buffer.append(" AUX ("); 488 for (int i=0; i < auxiliaryClasses.length; i++) 489 { 490 if (i >0) 491 { 492 buffer.append(" $ "); 493 } 494 else 495 { 496 buffer.append(' '); 497 } 498 buffer.append(auxiliaryClasses[i]); 499 } 500 buffer.append(" )"); 501 } 502 503 if (requiredAttributes.length == 1) 504 { 505 buffer.append(" MUST "); 506 buffer.append(requiredAttributes[0]); 507 } 508 else if (requiredAttributes.length > 1) 509 { 510 buffer.append(" MUST ("); 511 for (int i=0; i < requiredAttributes.length; i++) 512 { 513 if (i >0) 514 { 515 buffer.append(" $ "); 516 } 517 else 518 { 519 buffer.append(' '); 520 } 521 buffer.append(requiredAttributes[i]); 522 } 523 buffer.append(" )"); 524 } 525 526 if (optionalAttributes.length == 1) 527 { 528 buffer.append(" MAY "); 529 buffer.append(optionalAttributes[0]); 530 } 531 else if (optionalAttributes.length > 1) 532 { 533 buffer.append(" MAY ("); 534 for (int i=0; i < optionalAttributes.length; i++) 535 { 536 if (i > 0) 537 { 538 buffer.append(" $ "); 539 } 540 else 541 { 542 buffer.append(' '); 543 } 544 buffer.append(optionalAttributes[i]); 545 } 546 buffer.append(" )"); 547 } 548 549 if (prohibitedAttributes.length == 1) 550 { 551 buffer.append(" NOT "); 552 buffer.append(prohibitedAttributes[0]); 553 } 554 else if (prohibitedAttributes.length > 1) 555 { 556 buffer.append(" NOT ("); 557 for (int i=0; i < prohibitedAttributes.length; i++) 558 { 559 if (i > 0) 560 { 561 buffer.append(" $ "); 562 } 563 else 564 { 565 buffer.append(' '); 566 } 567 buffer.append(prohibitedAttributes[i]); 568 } 569 buffer.append(" )"); 570 } 571 572 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 573 { 574 final String name = e.getKey(); 575 final String[] values = e.getValue(); 576 if (values.length == 1) 577 { 578 buffer.append(' '); 579 buffer.append(name); 580 buffer.append(" '"); 581 encodeValue(values[0], buffer); 582 buffer.append('\''); 583 } 584 else 585 { 586 buffer.append(' '); 587 buffer.append(name); 588 buffer.append(" ("); 589 for (final String value : values) 590 { 591 buffer.append(" '"); 592 encodeValue(value, buffer); 593 buffer.append('\''); 594 } 595 buffer.append(" )"); 596 } 597 } 598 599 buffer.append(" )"); 600 } 601 602 603 604 /** 605 * Retrieves the OID for the structural object class associated with this 606 * DIT content rule. 607 * 608 * @return The OID for the structural object class associated with this DIT 609 * content rule. 610 */ 611 public String getOID() 612 { 613 return oid; 614 } 615 616 617 618 /** 619 * Retrieves the set of names for this DIT content rule. 620 * 621 * @return The set of names for this DIT content rule, or an empty array if 622 * it does not have any names. 623 */ 624 public String[] getNames() 625 { 626 return names; 627 } 628 629 630 631 /** 632 * Retrieves the primary name that can be used to reference this DIT content 633 * rule. If one or more names are defined, then the first name will be used. 634 * Otherwise, the structural object class OID will be returned. 635 * 636 * @return The primary name that can be used to reference this DIT content 637 * rule. 638 */ 639 public String getNameOrOID() 640 { 641 if (names.length == 0) 642 { 643 return oid; 644 } 645 else 646 { 647 return names[0]; 648 } 649 } 650 651 652 653 /** 654 * Indicates whether the provided string matches the OID or any of the names 655 * for this DIT content rule. 656 * 657 * @param s The string for which to make the determination. It must not be 658 * {@code null}. 659 * 660 * @return {@code true} if the provided string matches the OID or any of the 661 * names for this DIT content rule, or {@code false} if not. 662 */ 663 public boolean hasNameOrOID(final String s) 664 { 665 for (final String name : names) 666 { 667 if (s.equalsIgnoreCase(name)) 668 { 669 return true; 670 } 671 } 672 673 return s.equalsIgnoreCase(oid); 674 } 675 676 677 678 /** 679 * Retrieves the description for this DIT content rule, if available. 680 * 681 * @return The description for this DIT content rule, or {@code null} if 682 * there is no description defined. 683 */ 684 public String getDescription() 685 { 686 return description; 687 } 688 689 690 691 /** 692 * Indicates whether this DIT content rule is declared obsolete. 693 * 694 * @return {@code true} if this DIT content rule is declared obsolete, or 695 * {@code false} if it is not. 696 */ 697 public boolean isObsolete() 698 { 699 return isObsolete; 700 } 701 702 703 704 /** 705 * Retrieves the names or OIDs of the auxiliary object classes that may be 706 * present in entries containing the structural class for this DIT content 707 * rule. 708 * 709 * @return The names or OIDs of the auxiliary object classes that may be 710 * present in entries containing the structural class for this DIT 711 * content rule. 712 */ 713 public String[] getAuxiliaryClasses() 714 { 715 return auxiliaryClasses; 716 } 717 718 719 720 /** 721 * Retrieves the names or OIDs of the attributes that are required to be 722 * present in entries containing the structural object class for this DIT 723 * content rule. 724 * 725 * @return The names or OIDs of the attributes that are required to be 726 * present in entries containing the structural object class for this 727 * DIT content rule, or an empty array if there are no required 728 * attributes. 729 */ 730 public String[] getRequiredAttributes() 731 { 732 return requiredAttributes; 733 } 734 735 736 737 /** 738 * Retrieves the names or OIDs of the attributes that are optionally allowed 739 * to be present in entries containing the structural object class for this 740 * DIT content rule. 741 * 742 * @return The names or OIDs of the attributes that are optionally allowed to 743 * be present in entries containing the structural object class for 744 * this DIT content rule, or an empty array if there are no required 745 * attributes. 746 */ 747 public String[] getOptionalAttributes() 748 { 749 return optionalAttributes; 750 } 751 752 753 754 /** 755 * Retrieves the names or OIDs of the attributes that are not allowed to be 756 * present in entries containing the structural object class for this DIT 757 * content rule. 758 * 759 * @return The names or OIDs of the attributes that are not allowed to be 760 * present in entries containing the structural object class for this 761 * DIT content rule, or an empty array if there are no required 762 * attributes. 763 */ 764 public String[] getProhibitedAttributes() 765 { 766 return prohibitedAttributes; 767 } 768 769 770 771 /** 772 * Retrieves the set of extensions for this DIT content rule. They will be 773 * mapped from the extension name (which should start with "X-") to the set of 774 * values for that extension. 775 * 776 * @return The set of extensions for this DIT content rule. 777 */ 778 public Map<String,String[]> getExtensions() 779 { 780 return extensions; 781 } 782 783 784 785 /** 786 * {@inheritDoc} 787 */ 788 @Override() 789 public int hashCode() 790 { 791 return oid.hashCode(); 792 } 793 794 795 796 /** 797 * {@inheritDoc} 798 */ 799 @Override() 800 public boolean equals(final Object o) 801 { 802 if (o == null) 803 { 804 return false; 805 } 806 807 if (o == this) 808 { 809 return true; 810 } 811 812 if (! (o instanceof DITContentRuleDefinition)) 813 { 814 return false; 815 } 816 817 final DITContentRuleDefinition d = (DITContentRuleDefinition) o; 818 return (oid.equals(d.oid) && 819 stringsEqualIgnoreCaseOrderIndependent(names, d.names) && 820 stringsEqualIgnoreCaseOrderIndependent(auxiliaryClasses, 821 d.auxiliaryClasses) && 822 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes, 823 d.requiredAttributes) && 824 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes, 825 d.optionalAttributes) && 826 stringsEqualIgnoreCaseOrderIndependent(prohibitedAttributes, 827 d.prohibitedAttributes) && 828 bothNullOrEqualIgnoreCase(description, d.description) && 829 (isObsolete == d.isObsolete) && 830 extensionsEqual(extensions, d.extensions)); 831 } 832 833 834 835 /** 836 * Retrieves a string representation of this DIT content rule definition, in 837 * the format described in RFC 4512 section 4.1.6. 838 * 839 * @return A string representation of this DIT content rule definition. 840 */ 841 @Override() 842 public String toString() 843 { 844 return ditContentRuleString; 845 } 846}