001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.schema; 028 import org.opends.messages.Message; 029 030 031 032 import java.util.LinkedHashMap; 033 import java.util.LinkedHashSet; 034 import java.util.LinkedList; 035 import java.util.List; 036 import java.util.Map; 037 import java.util.Set; 038 039 import org.opends.server.admin.std.server.AttributeSyntaxCfg; 040 import org.opends.server.api.ApproximateMatchingRule; 041 import org.opends.server.api.AttributeSyntax; 042 import org.opends.server.api.EqualityMatchingRule; 043 import org.opends.server.api.OrderingMatchingRule; 044 import org.opends.server.api.SubstringMatchingRule; 045 import org.opends.server.config.ConfigException; 046 import org.opends.server.core.DirectoryServer; 047 import org.opends.server.types.AttributeType; 048 import org.opends.server.types.ByteString; 049 import org.opends.server.types.DirectoryException; 050 import org.opends.server.types.InitializationException; 051 import org.opends.server.types.ObjectClass; 052 import org.opends.server.types.ObjectClassType; 053 import org.opends.server.types.ResultCode; 054 import org.opends.server.types.Schema; 055 056 import static org.opends.server.loggers.debug.DebugLogger.*; 057 import org.opends.server.loggers.debug.DebugTracer; 058 import org.opends.server.types.DebugLogLevel; 059 import static org.opends.messages.SchemaMessages.*; 060 import org.opends.messages.MessageBuilder; 061 import static org.opends.server.schema.SchemaConstants.*; 062 import static org.opends.server.util.ServerConstants.*; 063 import static org.opends.server.util.StaticUtils.*; 064 065 066 067 /** 068 * This class implements the object class description syntax, which is used to 069 * hold objectclass definitions in the server schema. The format of this 070 * syntax is defined in RFC 2252. 071 */ 072 public class ObjectClassSyntax 073 extends AttributeSyntax<AttributeSyntaxCfg> 074 { 075 /** 076 * The tracer object for the debug logger. 077 */ 078 private static final DebugTracer TRACER = getTracer(); 079 080 081 082 // The default equality matching rule for this syntax. 083 private EqualityMatchingRule defaultEqualityMatchingRule; 084 085 // The default ordering matching rule for this syntax. 086 private OrderingMatchingRule defaultOrderingMatchingRule; 087 088 // The default substring matching rule for this syntax. 089 private SubstringMatchingRule defaultSubstringMatchingRule; 090 091 092 093 /** 094 * Creates a new instance of this syntax. Note that the only thing that 095 * should be done here is to invoke the default constructor for the 096 * superclass. All initialization should be performed in the 097 * <CODE>initializeSyntax</CODE> method. 098 */ 099 public ObjectClassSyntax() 100 { 101 super(); 102 } 103 104 105 106 /** 107 * {@inheritDoc} 108 */ 109 public void initializeSyntax(AttributeSyntaxCfg configuration) 110 throws ConfigException, InitializationException 111 { 112 defaultEqualityMatchingRule = 113 DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID); 114 if (defaultEqualityMatchingRule == null) 115 { 116 Message message = ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get( 117 EMR_CASE_IGNORE_OID, SYNTAX_OBJECTCLASS_NAME); 118 throw new InitializationException(message); 119 } 120 121 defaultOrderingMatchingRule = 122 DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID); 123 if (defaultOrderingMatchingRule == null) 124 { 125 Message message = ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get( 126 OMR_CASE_IGNORE_OID, SYNTAX_OBJECTCLASS_NAME); 127 throw new InitializationException(message); 128 } 129 130 defaultSubstringMatchingRule = 131 DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID); 132 if (defaultSubstringMatchingRule == null) 133 { 134 Message message = ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get( 135 SMR_CASE_IGNORE_OID, SYNTAX_OBJECTCLASS_NAME); 136 throw new InitializationException(message); 137 } 138 } 139 140 141 142 /** 143 * {@inheritDoc} 144 */ 145 public String getSyntaxName() 146 { 147 return SYNTAX_OBJECTCLASS_NAME; 148 } 149 150 151 152 /** 153 * {@inheritDoc} 154 */ 155 public String getOID() 156 { 157 return SYNTAX_OBJECTCLASS_OID; 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 public String getDescription() 166 { 167 return SYNTAX_OBJECTCLASS_DESCRIPTION; 168 } 169 170 171 172 /** 173 * {@inheritDoc} 174 */ 175 public EqualityMatchingRule getEqualityMatchingRule() 176 { 177 return defaultEqualityMatchingRule; 178 } 179 180 181 182 /** 183 * {@inheritDoc} 184 */ 185 public OrderingMatchingRule getOrderingMatchingRule() 186 { 187 return defaultOrderingMatchingRule; 188 } 189 190 191 192 /** 193 * {@inheritDoc} 194 */ 195 public SubstringMatchingRule getSubstringMatchingRule() 196 { 197 return defaultSubstringMatchingRule; 198 } 199 200 201 202 /** 203 * {@inheritDoc} 204 */ 205 public ApproximateMatchingRule getApproximateMatchingRule() 206 { 207 // There is no approximate matching rule by default. 208 return null; 209 } 210 211 212 213 /** 214 * {@inheritDoc} 215 */ 216 public boolean valueIsAcceptable(ByteString value, 217 MessageBuilder invalidReason) 218 { 219 // We'll use the decodeObjectClass method to determine if the value is 220 // acceptable. 221 try 222 { 223 decodeObjectClass(value, DirectoryServer.getSchema(), true); 224 return true; 225 } 226 catch (DirectoryException de) 227 { 228 if (debugEnabled()) 229 { 230 TRACER.debugCaught(DebugLogLevel.ERROR, de); 231 } 232 233 invalidReason.append(de.getMessageObject()); 234 return false; 235 } 236 } 237 238 239 240 /** 241 * Decodes the contents of the provided ASN.1 octet string as an objectclass 242 * definition according to the rules of this syntax. Note that the provided 243 * octet string value does not need to be normalized (and in fact, it should 244 * not be in order to allow the desired capitalization to be preserved). 245 * 246 * @param value The ASN.1 octet string containing the value 247 * to decode (it does not need to be 248 * normalized). 249 * @param schema The schema to use to resolve references to 250 * other schema elements. 251 * @param allowUnknownElements Indicates whether to allow values that 252 * reference a superior class or required or 253 * optional attribute types which are not 254 * defined in the server schema. This should 255 * only be true when called by 256 * {@code valueIsAcceptable}. 257 * 258 * @return The decoded objectclass definition. 259 * 260 * @throws DirectoryException If the provided value cannot be decoded as an 261 * objectclass definition. 262 */ 263 public static ObjectClass decodeObjectClass(ByteString value, Schema schema, 264 boolean allowUnknownElements) 265 throws DirectoryException 266 { 267 // Get string representations of the provided value using the provided form 268 // and with all lowercase characters. 269 String valueStr = value.stringValue(); 270 String lowerStr = toLowerCase(valueStr); 271 272 273 // We'll do this a character at a time. First, skip over any leading 274 // whitespace. 275 int pos = 0; 276 int length = valueStr.length(); 277 while ((pos < length) && (valueStr.charAt(pos) == ' ')) 278 { 279 pos++; 280 } 281 282 if (pos >= length) 283 { 284 // This means that the value was empty or contained only whitespace. That 285 // is illegal. 286 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE.get(); 287 throw new DirectoryException( 288 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 289 } 290 291 292 // The next character must be an open parenthesis. If it is not, then that 293 // is an error. 294 char c = valueStr.charAt(pos++); 295 if (c != '(') 296 { 297 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS. 298 get(valueStr, (pos-1), String.valueOf(c)); 299 throw new DirectoryException( 300 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 301 } 302 303 304 // Skip over any spaces immediately following the opening parenthesis. 305 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 306 { 307 pos++; 308 } 309 310 if (pos >= length) 311 { 312 // This means that the end of the value was reached before we could find 313 // the OID. Ths is illegal. 314 Message message = 315 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 316 throw new DirectoryException( 317 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 318 } 319 320 321 // The next set of characters must be the OID. Strictly speaking, this 322 // should only be a numeric OID, but we'll also allow for the 323 // "ocname-oid" case as well. Look at the first character to figure out 324 // which we will be using. 325 int oidStartPos = pos; 326 if (isDigit(c)) 327 { 328 // This must be a numeric OID. In that case, we will accept only digits 329 // and periods, but not consecutive periods. 330 boolean lastWasPeriod = false; 331 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' ')) 332 { 333 if (c == '.') 334 { 335 if (lastWasPeriod) 336 { 337 Message message = 338 ERR_ATTR_SYNTAX_OBJECTCLASS_DOUBLE_PERIOD_IN_NUMERIC_OID. 339 get(valueStr, (pos-1)); 340 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 341 message); 342 } 343 else 344 { 345 lastWasPeriod = true; 346 } 347 } 348 else if (! isDigit(c)) 349 { 350 // This must have been an illegal character. 351 Message message = 352 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR_IN_NUMERIC_OID. 353 get(valueStr, String.valueOf(c), (pos-1)); 354 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 355 message); 356 } 357 else 358 { 359 lastWasPeriod = false; 360 } 361 } 362 } 363 else 364 { 365 // This must be a "fake" OID. In this case, we will only accept 366 // alphabetic characters, numeric digits, and the hyphen. 367 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' ')) 368 { 369 if (isAlpha(c) || isDigit(c) || (c == '-') || 370 ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) 371 { 372 // This is fine. It is an acceptable character. 373 } 374 else 375 { 376 // This must have been an illegal character. 377 Message message = 378 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR_IN_STRING_OID. 379 get(valueStr, String.valueOf(c), (pos-1)); 380 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 381 message); 382 } 383 } 384 } 385 386 387 // If we're at the end of the value, then it isn't a valid objectclass 388 // description. Otherwise, parse out the OID. 389 String oid; 390 if (pos >= length) 391 { 392 Message message = 393 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 394 throw new DirectoryException( 395 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 396 } 397 else 398 { 399 oid = lowerStr.substring(oidStartPos, (pos-1)); 400 } 401 402 403 // Skip over the space(s) after the OID. 404 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 405 { 406 pos++; 407 } 408 409 if (pos >= length) 410 { 411 // This means that the end of the value was reached before we could find 412 // the OID. Ths is illegal. 413 Message message = 414 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 415 throw new DirectoryException( 416 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 417 } 418 419 420 // At this point, we should have a pretty specific syntax that describes 421 // what may come next, but some of the components are optional and it would 422 // be pretty easy to put something in the wrong order, so we will be very 423 // flexible about what we can accept. Just look at the next token, figure 424 // out what it is and how to treat what comes after it, then repeat until 425 // we get to the end of the value. But before we start, set default values 426 // for everything else we might need to know. 427 String primaryName = oid; 428 List<String> names = new LinkedList<String>(); 429 String description = null; 430 boolean isObsolete = false; 431 ObjectClass superiorClass = DirectoryServer.getTopObjectClass(); 432 Set<AttributeType> requiredAttributes = new LinkedHashSet<AttributeType>(); 433 Set<AttributeType> optionalAttributes = new LinkedHashSet<AttributeType>(); 434 ObjectClassType objectClassType = superiorClass 435 .getObjectClassType(); 436 Map<String, List<String>> extraProperties = 437 new LinkedHashMap<String, List<String>>(); 438 439 440 while (true) 441 { 442 StringBuilder tokenNameBuffer = new StringBuilder(); 443 pos = readTokenName(valueStr, tokenNameBuffer, pos); 444 String tokenName = tokenNameBuffer.toString(); 445 String lowerTokenName = toLowerCase(tokenName); 446 if (tokenName.equals(")")) 447 { 448 // We must be at the end of the value. If not, then that's a problem. 449 if (pos < length) 450 { 451 Message message = 452 ERR_ATTR_SYNTAX_OBJECTCLASS_UNEXPECTED_CLOSE_PARENTHESIS. 453 get(valueStr, (pos-1)); 454 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 455 message); 456 } 457 458 break; 459 } 460 else if (lowerTokenName.equals("name")) 461 { 462 // This specifies the set of names for the objectclass. It may be a 463 // single name in single quotes, or it may be an open parenthesis 464 // followed by one or more names in single quotes separated by spaces. 465 c = valueStr.charAt(pos++); 466 if (c == '\'') 467 { 468 StringBuilder userBuffer = new StringBuilder(); 469 StringBuilder lowerBuffer = new StringBuilder(); 470 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 471 (pos-1)); 472 primaryName = userBuffer.toString(); 473 names.add(primaryName); 474 } 475 else if (c == '(') 476 { 477 StringBuilder userBuffer = new StringBuilder(); 478 StringBuilder lowerBuffer = new StringBuilder(); 479 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 480 pos); 481 primaryName = userBuffer.toString(); 482 names.add(primaryName); 483 484 485 while (true) 486 { 487 if (valueStr.charAt(pos) == ')') 488 { 489 // Skip over any spaces after the parenthesis. 490 pos++; 491 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 492 { 493 pos++; 494 } 495 496 break; 497 } 498 else 499 { 500 userBuffer = new StringBuilder(); 501 lowerBuffer = new StringBuilder(); 502 503 pos = readQuotedString(valueStr, lowerStr, userBuffer, 504 lowerBuffer, pos); 505 names.add(userBuffer.toString()); 506 } 507 } 508 } 509 else 510 { 511 // This is an illegal character. 512 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR.get( 513 valueStr, String.valueOf(c), (pos-1)); 514 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 515 message); 516 } 517 } 518 else if (lowerTokenName.equals("desc")) 519 { 520 // This specifies the description for the objectclass. It is an 521 // arbitrary string of characters enclosed in single quotes. 522 StringBuilder descriptionBuffer = new StringBuilder(); 523 pos = readQuotedString(valueStr, descriptionBuffer, pos); 524 description = descriptionBuffer.toString(); 525 } 526 else if (lowerTokenName.equals("obsolete")) 527 { 528 // This indicates whether the objectclass should be considered obsolete. 529 // We do not need to do any more parsing for this token. 530 isObsolete = true; 531 } 532 else if (lowerTokenName.equals("sup")) 533 { 534 // This specifies the name or OID of the superior objectclass from which 535 // this objectclass should inherit its properties. 536 StringBuilder woidBuffer = new StringBuilder(); 537 pos = readWOID(lowerStr, woidBuffer, pos); 538 superiorClass = schema.getObjectClass(woidBuffer.toString()); 539 if (superiorClass == null) 540 { 541 if (allowUnknownElements) 542 { 543 superiorClass = 544 DirectoryServer.getDefaultObjectClass(woidBuffer.toString()); 545 } 546 else 547 { 548 // This is bad because we don't know what the superior objectclass 549 // is so we can't base this objectclass on it. 550 Message message = 551 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_SUPERIOR_CLASS. 552 get(String.valueOf(oid), String.valueOf(woidBuffer)); 553 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 554 message); 555 } 556 } 557 558 559 // Use the objectclass type of the superior objectclass to override the 560 // objectclass type for the new one. This could potentially cause a 561 // problem if the components in the objectclass description are provided 562 // out-of-order, but even if that happens then it doesn't really matter 563 // since the objectclass type currently isn't used for anything anyway. 564 objectClassType = superiorClass.getObjectClassType(); 565 } 566 else if (lowerTokenName.equals("abstract")) 567 { 568 // This indicates that entries must not include this objectclass unless 569 // they also include a non-abstract objectclass that inherits from this 570 // class. We do not need any more parsing for this token. 571 objectClassType = ObjectClassType.ABSTRACT; 572 } 573 else if (lowerTokenName.equals("structural")) 574 { 575 // This indicates that this is a structural objectclass. We do not need 576 // any more parsing for this token. 577 objectClassType = ObjectClassType.STRUCTURAL; 578 } 579 else if (lowerTokenName.equals("auxiliary")) 580 { 581 // This indicates that this is an auxiliary objectclass. We do not need 582 // any more parsing for this token. 583 objectClassType = ObjectClassType.AUXILIARY; 584 } 585 else if (lowerTokenName.equals("must")) 586 { 587 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>(); 588 589 // This specifies the set of required attributes for the objectclass. 590 // It may be a single name or OID (not in quotes), or it may be an 591 // open parenthesis followed by one or more names separated by spaces 592 // and the dollar sign character, followed by a closing parenthesis. 593 c = valueStr.charAt(pos++); 594 if (c == '(') 595 { 596 while (true) 597 { 598 StringBuilder woidBuffer = new StringBuilder(); 599 pos = readWOID(lowerStr, woidBuffer, (pos)); 600 601 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 602 if (attr == null) 603 { 604 if (allowUnknownElements) 605 { 606 attr = DirectoryServer.getDefaultAttributeType( 607 woidBuffer.toString()); 608 } 609 else 610 { 611 // This isn't good because it means that the objectclass 612 // requires an attribute type that we don't know anything about. 613 Message message = 614 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR. 615 get(oid, woidBuffer.toString()); 616 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 617 message); 618 } 619 } 620 621 attrs.add(attr); 622 623 624 // The next character must be either a dollar sign or a closing 625 // parenthesis. 626 c = valueStr.charAt(pos++); 627 if (c == ')') 628 { 629 // This denotes the end of the list. 630 break; 631 } 632 else if (c != '$') 633 { 634 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR.get( 635 valueStr, String.valueOf(c), (pos-1)); 636 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 637 message); 638 } 639 } 640 } 641 else 642 { 643 StringBuilder woidBuffer = new StringBuilder(); 644 pos = readWOID(lowerStr, woidBuffer, (pos-1)); 645 646 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 647 if (attr == null) 648 { 649 if (allowUnknownElements) 650 { 651 attr = DirectoryServer.getDefaultAttributeType( 652 woidBuffer.toString()); 653 } 654 else 655 { 656 // This isn't good because it means that the objectclass requires 657 // an attribute type that we don't know anything about. 658 Message message = 659 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR. 660 get(oid, woidBuffer.toString()); 661 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 662 message); 663 } 664 } 665 666 attrs.add(attr); 667 } 668 669 requiredAttributes.addAll(attrs); 670 } 671 else if (lowerTokenName.equals("may")) 672 { 673 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>(); 674 675 // This specifies the set of optional attributes for the objectclass. 676 // It may be a single name or OID (not in quotes), or it may be an 677 // open parenthesis followed by one or more names separated by spaces 678 // and the dollar sign character, followed by a closing parenthesis. 679 c = valueStr.charAt(pos++); 680 if (c == '(') 681 { 682 while (true) 683 { 684 StringBuilder woidBuffer = new StringBuilder(); 685 pos = readWOID(lowerStr, woidBuffer, (pos)); 686 687 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 688 if (attr == null) 689 { 690 if (allowUnknownElements) 691 { 692 attr = DirectoryServer.getDefaultAttributeType( 693 woidBuffer.toString()); 694 } 695 else 696 { 697 // This isn't good because it means that the objectclass allows 698 // an attribute type that we don't know anything about. 699 Message message = 700 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR. 701 get(oid, woidBuffer.toString()); 702 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 703 message); 704 } 705 } 706 707 attrs.add(attr); 708 709 710 // The next character must be either a dollar sign or a closing 711 // parenthesis. 712 c = valueStr.charAt(pos++); 713 if (c == ')') 714 { 715 // This denotes the end of the list. 716 break; 717 } 718 else if (c != '$') 719 { 720 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR.get( 721 valueStr, String.valueOf(c), (pos-1)); 722 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 723 message); 724 } 725 } 726 } 727 else 728 { 729 StringBuilder woidBuffer = new StringBuilder(); 730 pos = readWOID(lowerStr, woidBuffer, (pos-1)); 731 732 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 733 if (attr == null) 734 { 735 if (allowUnknownElements) 736 { 737 attr = DirectoryServer.getDefaultAttributeType( 738 woidBuffer.toString()); 739 } 740 else 741 { 742 // This isn't good because it means that the objectclass allows an 743 // attribute type that we don't know anything about. 744 Message message = 745 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR. 746 get(oid, woidBuffer.toString()); 747 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 748 message); 749 } 750 } 751 752 attrs.add(attr); 753 } 754 755 optionalAttributes.addAll(attrs); 756 } 757 else 758 { 759 // This must be a non-standard property and it must be followed by 760 // either a single value in single quotes or an open parenthesis 761 // followed by one or more values in single quotes separated by spaces 762 // followed by a close parenthesis. 763 List<String> valueList = new LinkedList<String>(); 764 pos = readExtraParameterValues(valueStr, valueList, pos); 765 extraProperties.put(tokenName, valueList); 766 } 767 } 768 769 770 if (superiorClass.getOID().equals(oid)) 771 { 772 // This should only happen for the "top" objectclass. 773 superiorClass = null; 774 } 775 else 776 { 777 // Make sure that the inheritance configuration is acceptable. 778 ObjectClassType superiorType = superiorClass.getObjectClassType(); 779 switch (objectClassType) 780 { 781 case ABSTRACT: 782 // Abstract classes may only inherit from other abstract classes. 783 if (superiorType != ObjectClassType.ABSTRACT) 784 { 785 Message message = 786 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE. 787 get(oid, objectClassType.toString(), superiorType.toString(), 788 superiorClass.getNameOrOID()); 789 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 790 message); 791 } 792 break; 793 794 case AUXILIARY: 795 // Auxiliary classes may only inherit from abstract classes or other 796 // auxiliary classes. 797 if ((superiorType != ObjectClassType.ABSTRACT) && 798 (superiorType != ObjectClassType.AUXILIARY)) 799 { 800 Message message = 801 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE. 802 get(oid, objectClassType.toString(), superiorType.toString(), 803 superiorClass.getNameOrOID()); 804 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 805 message); 806 } 807 break; 808 809 case STRUCTURAL: 810 // Structural classes may only inherit from abstract classes or other 811 // structural classes. 812 if ((superiorType != ObjectClassType.ABSTRACT) && 813 (superiorType != ObjectClassType.STRUCTURAL)) 814 { 815 Message message = 816 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE. 817 get(oid, objectClassType.toString(), superiorType.toString(), 818 superiorClass.getNameOrOID()); 819 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 820 message); 821 } 822 823 // Structural classes must have the "top" objectclass somewhere in the 824 // superior chain. 825 if (! superiorChainIncludesTop(superiorClass)) 826 { 827 Message message = 828 WARN_ATTR_SYNTAX_OBJECTCLASS_STRUCTURAL_SUPERIOR_NOT_TOP. 829 get(oid); 830 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 831 message); 832 } 833 break; 834 } 835 } 836 837 838 839 return new ObjectClass(value.stringValue(), primaryName, names, oid, 840 description, superiorClass, requiredAttributes, 841 optionalAttributes, objectClassType, isObsolete, 842 extraProperties); 843 } 844 845 846 847 /** 848 * Reads the next token name from the objectclass definition, skipping over 849 * any leading or trailing spaces, and appends it to the provided buffer. 850 * 851 * @param valueStr The string representation of the objectclass definition. 852 * @param tokenName The buffer into which the token name will be written. 853 * @param startPos The position in the provided string at which to start 854 * reading the token name. 855 * 856 * @return The position of the first character that is not part of the token 857 * name or one of the trailing spaces after it. 858 * 859 * @throws DirectoryException If a problem is encountered while reading the 860 * token name. 861 */ 862 private static int readTokenName(String valueStr, StringBuilder tokenName, 863 int startPos) 864 throws DirectoryException 865 { 866 // Skip over any spaces at the beginning of the value. 867 char c = '\u0000'; 868 int length = valueStr.length(); 869 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 870 { 871 startPos++; 872 } 873 874 if (startPos >= length) 875 { 876 Message message = 877 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 878 throw new DirectoryException( 879 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 880 } 881 882 883 // Read until we find the next space. 884 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' ')) 885 { 886 tokenName.append(c); 887 } 888 889 890 // Skip over any trailing spaces after the value. 891 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 892 { 893 startPos++; 894 } 895 896 897 // Return the position of the first non-space character after the token. 898 return startPos; 899 } 900 901 902 903 /** 904 * Reads the value of a string enclosed in single quotes, skipping over the 905 * quotes and any leading or trailing spaces, and appending the string to the 906 * provided buffer. 907 * 908 * @param valueStr The user-provided representation of the objectclass 909 * definition. 910 * @param valueBuffer The buffer into which the user-provided representation 911 * of the value will be placed. 912 * @param startPos The position in the provided string at which to start 913 * reading the quoted string. 914 * 915 * @return The position of the first character that is not part of the quoted 916 * string or one of the trailing spaces after it. 917 * 918 * @throws DirectoryException If a problem is encountered while reading the 919 * quoted string. 920 */ 921 private static int readQuotedString(String valueStr, 922 StringBuilder valueBuffer, int startPos) 923 throws DirectoryException 924 { 925 // Skip over any spaces at the beginning of the value. 926 char c = '\u0000'; 927 int length = valueStr.length(); 928 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 929 { 930 startPos++; 931 } 932 933 if (startPos >= length) 934 { 935 Message message = 936 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 937 throw new DirectoryException( 938 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 939 } 940 941 942 // The next character must be a single quote. 943 if (c != '\'') 944 { 945 Message message = WARN_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_QUOTE_AT_POS.get( 946 valueStr, startPos, String.valueOf(c)); 947 throw new DirectoryException( 948 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 949 } 950 951 952 // Read until we find the closing quote. 953 startPos++; 954 while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\'')) 955 { 956 valueBuffer.append(c); 957 startPos++; 958 } 959 960 961 // Skip over any trailing spaces after the value. 962 startPos++; 963 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 964 { 965 startPos++; 966 } 967 968 969 // If we're at the end of the value, then that's illegal. 970 if (startPos >= length) 971 { 972 Message message = 973 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 974 throw new DirectoryException( 975 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 976 } 977 978 979 // Return the position of the first non-space character after the token. 980 return startPos; 981 } 982 983 984 985 /** 986 * Reads the value of a string enclosed in single quotes, skipping over the 987 * quotes and any leading or trailing spaces, and appending the string to the 988 * provided buffer. 989 * 990 * @param valueStr The user-provided representation of the objectclass 991 * definition. 992 * @param lowerStr The all-lowercase representation of the objectclass 993 * definition. 994 * @param userBuffer The buffer into which the user-provided representation 995 * of the value will be placed. 996 * @param lowerBuffer The buffer into which the all-lowercase representation 997 * of the value will be placed. 998 * @param startPos The position in the provided string at which to start 999 * reading the quoted string. 1000 * 1001 * @return The position of the first character that is not part of the quoted 1002 * string or one of the trailing spaces after it. 1003 * 1004 * @throws DirectoryException If a problem is encountered while reading the 1005 * quoted string. 1006 */ 1007 private static int readQuotedString(String valueStr, String lowerStr, 1008 StringBuilder userBuffer, 1009 StringBuilder lowerBuffer, int startPos) 1010 throws DirectoryException 1011 { 1012 // Skip over any spaces at the beginning of the value. 1013 char c = '\u0000'; 1014 int length = lowerStr.length(); 1015 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1016 { 1017 startPos++; 1018 } 1019 1020 if (startPos >= length) 1021 { 1022 Message message = 1023 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(lowerStr); 1024 throw new DirectoryException( 1025 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1026 } 1027 1028 1029 // The next character must be a single quote. 1030 if (c != '\'') 1031 { 1032 Message message = WARN_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_QUOTE_AT_POS.get( 1033 valueStr, startPos, String.valueOf(c)); 1034 throw new DirectoryException( 1035 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1036 } 1037 1038 1039 // Read until we find the closing quote. 1040 startPos++; 1041 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) != '\'')) 1042 { 1043 lowerBuffer.append(c); 1044 userBuffer.append(valueStr.charAt(startPos)); 1045 startPos++; 1046 } 1047 1048 1049 // Skip over any trailing spaces after the value. 1050 startPos++; 1051 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1052 { 1053 startPos++; 1054 } 1055 1056 1057 // If we're at the end of the value, then that's illegal. 1058 if (startPos >= length) 1059 { 1060 Message message = 1061 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(lowerStr); 1062 throw new DirectoryException( 1063 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1064 } 1065 1066 1067 // Return the position of the first non-space character after the token. 1068 return startPos; 1069 } 1070 1071 1072 1073 /** 1074 * Reads the attribute type/objectclass description or numeric OID from the 1075 * provided string, skipping over any leading or trailing spaces, and 1076 * appending the value to the provided buffer. 1077 * 1078 * @param lowerStr The string from which the name or OID is to be read. 1079 * @param woidBuffer The buffer into which the name or OID should be 1080 * appended. 1081 * @param startPos The position at which to start reading. 1082 * 1083 * @return The position of the first character after the name or OID that is 1084 * not a space. 1085 * 1086 * @throws DirectoryException If a problem is encountered while reading the 1087 * name or OID. 1088 */ 1089 private static int readWOID(String lowerStr, StringBuilder woidBuffer, 1090 int startPos) 1091 throws DirectoryException 1092 { 1093 // Skip over any spaces at the beginning of the value. 1094 char c = '\u0000'; 1095 int length = lowerStr.length(); 1096 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1097 { 1098 startPos++; 1099 } 1100 1101 if (startPos >= length) 1102 { 1103 Message message = 1104 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(lowerStr); 1105 throw new DirectoryException( 1106 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1107 } 1108 1109 1110 // The next character must be either numeric (for an OID) or alphabetic (for 1111 // an objectclass description). 1112 if (isDigit(c)) 1113 { 1114 // This must be a numeric OID. In that case, we will accept only digits 1115 // and periods, but not consecutive periods. 1116 boolean lastWasPeriod = false; 1117 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' ')) 1118 { 1119 if (c == '.') 1120 { 1121 if (lastWasPeriod) 1122 { 1123 Message message = 1124 ERR_ATTR_SYNTAX_OBJECTCLASS_DOUBLE_PERIOD_IN_NUMERIC_OID. 1125 get(lowerStr, (startPos-1)); 1126 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1127 message); 1128 } 1129 else 1130 { 1131 woidBuffer.append(c); 1132 lastWasPeriod = true; 1133 } 1134 } 1135 else if (! isDigit(c)) 1136 { 1137 // Technically, this must be an illegal character. However, it is 1138 // possible that someone just got sloppy and did not include a space 1139 // between the name/OID and a closing parenthesis. In that case, 1140 // we'll assume it's the end of the value. What's more, we'll have 1141 // to prematurely return to nasty side effects from stripping off 1142 // additional characters. 1143 if (c == ')') 1144 { 1145 return (startPos-1); 1146 } 1147 1148 // This must have been an illegal character. 1149 Message message = 1150 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR_IN_NUMERIC_OID. 1151 get(lowerStr, String.valueOf(c), (startPos-1)); 1152 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1153 message); 1154 } 1155 else 1156 { 1157 woidBuffer.append(c); 1158 lastWasPeriod = false; 1159 } 1160 } 1161 } 1162 else if (isAlpha(c)) 1163 { 1164 // This must be an objectclass description. In this case, we will only 1165 // accept alphabetic characters, numeric digits, and the hyphen. 1166 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' ')) 1167 { 1168 if (isAlpha(c) || isDigit(c) || (c == '-') || 1169 ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) 1170 { 1171 woidBuffer.append(c); 1172 } 1173 else 1174 { 1175 // Technically, this must be an illegal character. However, it is 1176 // possible that someone just got sloppy and did not include a space 1177 // between the name/OID and a closing parenthesis. In that case, 1178 // we'll assume it's the end of the value. What's more, we'll have 1179 // to prematurely return to nasty side effects from stripping off 1180 // additional characters. 1181 if (c == ')') 1182 { 1183 return (startPos-1); 1184 } 1185 1186 // This must have been an illegal character. 1187 Message message = 1188 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR_IN_STRING_OID. 1189 get(lowerStr, String.valueOf(c), (startPos-1)); 1190 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1191 message); 1192 } 1193 } 1194 } 1195 else 1196 { 1197 Message message = 1198 ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR.get( 1199 lowerStr, String.valueOf(c), startPos); 1200 throw new DirectoryException( 1201 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1202 } 1203 1204 1205 // Skip over any trailing spaces after the value. 1206 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1207 { 1208 startPos++; 1209 } 1210 1211 1212 // If we're at the end of the value, then that's illegal. 1213 if (startPos >= length) 1214 { 1215 Message message = 1216 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(lowerStr); 1217 throw new DirectoryException( 1218 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1219 } 1220 1221 1222 // Return the position of the first non-space character after the token. 1223 return startPos; 1224 } 1225 1226 1227 1228 /** 1229 * Reads the value for an "extra" parameter. It will handle a single unquoted 1230 * word (which is technically illegal, but we'll allow it), a single quoted 1231 * string, or an open parenthesis followed by a space-delimited set of quoted 1232 * strings or unquoted words followed by a close parenthesis. 1233 * 1234 * @param valueStr The string containing the information to be read. 1235 * @param valueList The list of "extra" parameter values read so far. 1236 * @param startPos The position in the value string at which to start 1237 * reading. 1238 * 1239 * @return The "extra" parameter value that was read. 1240 * 1241 * @throws DirectoryException If a problem occurs while attempting to read 1242 * the value. 1243 */ 1244 private static int readExtraParameterValues(String valueStr, 1245 List<String> valueList, int startPos) 1246 throws DirectoryException 1247 { 1248 // Skip over any leading spaces. 1249 int length = valueStr.length(); 1250 char c = valueStr.charAt(startPos++); 1251 while ((startPos < length) && (c == ' ')) 1252 { 1253 c = valueStr.charAt(startPos++); 1254 } 1255 1256 if (startPos >= length) 1257 { 1258 Message message = 1259 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 1260 throw new DirectoryException( 1261 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1262 } 1263 1264 1265 // Look at the next character. If it is a quote, then parse until the next 1266 // quote and end. If it is an open parenthesis, then parse individual 1267 // values until the close parenthesis and end. Otherwise, parse until the 1268 // next space and end. 1269 if (c == '\'') 1270 { 1271 // Parse until the closing quote. 1272 StringBuilder valueBuffer = new StringBuilder(); 1273 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != '\'')) 1274 { 1275 valueBuffer.append(c); 1276 } 1277 1278 valueList.add(valueBuffer.toString()); 1279 } 1280 else if (c == '(') 1281 { 1282 while (true) 1283 { 1284 // Skip over any leading spaces; 1285 startPos++; 1286 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 1287 { 1288 startPos++; 1289 } 1290 1291 if (startPos >= length) 1292 { 1293 Message message = 1294 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 1295 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1296 message); 1297 } 1298 1299 1300 if (c == ')') 1301 { 1302 // This is the end of the list. 1303 break; 1304 } 1305 else if (c == '(') 1306 { 1307 // This is an illegal character. 1308 Message message = ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_CHAR.get( 1309 valueStr, String.valueOf(c), startPos); 1310 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1311 message); 1312 } 1313 else 1314 { 1315 // We'll recursively call this method to deal with this. 1316 startPos = readExtraParameterValues(valueStr, valueList, startPos); 1317 } 1318 } 1319 } 1320 else 1321 { 1322 // Parse until the next space. 1323 StringBuilder valueBuffer = new StringBuilder(); 1324 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' ')) 1325 { 1326 valueBuffer.append(c); 1327 } 1328 1329 valueList.add(valueBuffer.toString()); 1330 } 1331 1332 1333 1334 // Skip over any trailing spaces. 1335 while ((startPos < length) && (valueStr.charAt(startPos) == ' ')) 1336 { 1337 startPos++; 1338 } 1339 1340 if (startPos >= length) 1341 { 1342 Message message = 1343 ERR_ATTR_SYNTAX_OBJECTCLASS_TRUNCATED_VALUE.get(valueStr); 1344 throw new DirectoryException( 1345 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1346 } 1347 1348 1349 return startPos; 1350 } 1351 1352 1353 1354 /** 1355 * Indicates whether the provided objectclass or any of its superiors is equal 1356 * to the "top" objectclass. 1357 * 1358 * @param superiorClass The objectclass for which to make the determination. 1359 * 1360 * @return {@code true} if the provided class or any of its superiors is 1361 * equal to the "top" objectclass, or {@code false} if not. 1362 */ 1363 private static boolean superiorChainIncludesTop(ObjectClass superiorClass) 1364 { 1365 if (superiorClass == null) 1366 { 1367 return false; 1368 } 1369 else if (superiorClass.hasName(OC_TOP)) 1370 { 1371 return true; 1372 } 1373 else 1374 { 1375 return superiorChainIncludesTop(superiorClass.getSuperiorClass()); 1376 } 1377 } 1378 } 1379