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