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