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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.schema; 028 029import static org.opends.messages.SchemaMessages.*; 030import static org.opends.server.config.ConfigConstants.*; 031import static org.opends.server.schema.SchemaConstants.*; 032import static org.opends.server.util.ServerConstants.*; 033import static org.opends.server.util.StaticUtils.*; 034 035import java.util.ArrayList; 036import java.util.HashMap; 037import java.util.LinkedHashMap; 038import java.util.LinkedList; 039import java.util.List; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.opendj.config.server.ConfigChangeResult; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.forgerock.opendj.ldap.ByteSequence; 045import org.forgerock.opendj.ldap.Option; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.forgerock.opendj.ldap.schema.AttributeUsage; 048import org.forgerock.opendj.ldap.schema.MatchingRule; 049import org.forgerock.opendj.ldap.schema.SchemaOptions; 050import org.forgerock.opendj.ldap.schema.Syntax; 051import org.opends.server.admin.server.ConfigurationChangeListener; 052import org.opends.server.admin.std.server.AttributeTypeDescriptionAttributeSyntaxCfg; 053import org.opends.server.api.AttributeSyntax; 054import org.opends.server.core.DirectoryServer; 055import org.opends.server.core.ServerContext; 056import org.opends.server.types.AttributeType; 057import org.opends.server.types.CommonSchemaElements; 058import org.opends.server.types.DirectoryException; 059import org.opends.server.types.InitializationException; 060import org.opends.server.types.Schema; 061 062/** 063 * This class defines the attribute type description syntax, which is used to 064 * hold attribute type definitions in the server schema. The format of this 065 * syntax is defined in RFC 2252. 066 */ 067public class AttributeTypeSyntax 068 extends AttributeSyntax<AttributeTypeDescriptionAttributeSyntaxCfg> 069 implements 070 ConfigurationChangeListener<AttributeTypeDescriptionAttributeSyntaxCfg> { 071 072 /** 073 * The reference to the configuration for this attribute type description 074 * syntax. 075 */ 076 private AttributeTypeDescriptionAttributeSyntaxCfg currentConfig; 077 078 079 080 /** If true strip the suggested minimum upper bound from the syntax OID. */ 081 private static boolean stripMinimumUpperBound; 082 083 private ServerContext serverContext; 084 085 086 /** 087 * Creates a new instance of this syntax. Note that the only thing that 088 * should be done here is to invoke the default constructor for the 089 * superclass. All initialization should be performed in the 090 * <CODE>initializeSyntax</CODE> method. 091 */ 092 public AttributeTypeSyntax() 093 { 094 super(); 095 } 096 097 098 099 /** {@inheritDoc} */ 100 @Override 101 public void 102 initializeSyntax(AttributeTypeDescriptionAttributeSyntaxCfg configuration, ServerContext serverContext) 103 throws ConfigException, InitializationException 104 { 105 this.serverContext = serverContext; 106 107 // This syntax is one of the Directory Server's core syntaxes and therefore 108 // it may be instantiated at times without a configuration entry. If that 109 // is the case, then we'll exit now before doing anything that could require 110 // access to that entry. 111 if (configuration == null) 112 { 113 return; 114 } 115 116 currentConfig = configuration; 117 currentConfig.addAttributeTypeDescriptionChangeListener(this); 118 stripMinimumUpperBound=configuration.isStripSyntaxMinUpperBound(); 119 updateNewSchema(); 120 } 121 122 /** Update the option in new schema if it changes from current value. */ 123 private void updateNewSchema() 124 { 125 Option<Boolean> option = SchemaOptions.STRIP_UPPER_BOUND_FOR_ATTRIBUTE_TYPE; 126 if (isStripSyntaxMinimumUpperBound() != serverContext.getSchemaNG().getOption(option)) 127 { 128 SchemaUpdater schemaUpdater = serverContext.getSchemaUpdater(); 129 schemaUpdater.updateSchema( 130 schemaUpdater.getSchemaBuilder().setOption(option, stripMinimumUpperBound).toSchema()); 131 } 132 } 133 134 /** {@inheritDoc} */ 135 @Override 136 public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema) 137 { 138 return schema.getSyntax(SchemaConstants.SYNTAX_ATTRIBUTE_TYPE_OID); 139 } 140 141 /** {@inheritDoc} */ 142 @Override 143 public String getName() 144 { 145 return SYNTAX_ATTRIBUTE_TYPE_NAME; 146 } 147 148 149 150 /** {@inheritDoc} */ 151 @Override 152 public String getOID() 153 { 154 return SYNTAX_ATTRIBUTE_TYPE_OID; 155 } 156 157 158 159 /** {@inheritDoc} */ 160 @Override 161 public String getDescription() 162 { 163 return SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION; 164 } 165 166 167 168 /** 169 * Decodes the contents of the provided ASN.1 octet string as an attribute 170 * type definition according to the rules of this syntax. Note that the 171 * provided octet string value does not need to be normalized (and in fact, it 172 * should not be in order to allow the desired capitalization to be 173 * preserved). 174 * 175 * @param value The ASN.1 octet string containing the value 176 * to decode (it does not need to be 177 * normalized). 178 * @param schema The schema to use to resolve references to 179 * other schema elements. 180 * @param allowUnknownElements Indicates whether to allow values that 181 * reference a superior attribute type which are 182 * not defined in the server schema. This should 183 * only be true when called by 184 * {@code valueIsAcceptable}. 185 * 186 * @return The decoded attribute type definition. 187 * 188 * @throws DirectoryException If the provided value cannot be decoded as an 189 * attribute type definition. 190 */ 191 public static AttributeType decodeAttributeType(ByteSequence value, 192 Schema schema, 193 boolean allowUnknownElements) 194 throws DirectoryException 195 { 196 // Get string representations of the provided value using the provided form 197 // and with all lowercase characters. 198 String valueStr = value.toString(); 199 String lowerStr = toLowerCase(valueStr); 200 201 202 // We'll do this a character at a time. First, skip over any leading 203 // whitespace. 204 int pos = 0; 205 int length = valueStr.length(); 206 while (pos < length && valueStr.charAt(pos) == ' ') 207 { 208 pos++; 209 } 210 211 if (pos >= length) 212 { 213 // This means that the value was empty or contained only whitespace. That 214 // is illegal. 215 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get(); 216 throw new DirectoryException( 217 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 218 } 219 220 221 // The next character must be an open parenthesis. If it is not, then that 222 // is an error. 223 char c = valueStr.charAt(pos++); 224 if (c != '(') 225 { 226 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(valueStr, pos - 1, c); 227 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 228 } 229 230 231 // Skip over any spaces immediately following the opening parenthesis. 232 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 233 { 234 pos++; 235 } 236 237 if (pos >= length) 238 { 239 // This means that the end of the value was reached before we could find 240 // the OID. Ths is illegal. 241 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 242 throw new DirectoryException( 243 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 244 } 245 246 247 // The next set of characters must be the OID. Strictly speaking, this 248 // should only be a numeric OID, but we'll also allow for the 249 // "attrname-oid" case as well. Look at the first character to figure out 250 // which we will be using. 251 int oidStartPos = pos; 252 if (isDigit(c)) 253 { 254 // This must be a numeric OID. In that case, we will accept only digits 255 // and periods, but not consecutive periods. 256 boolean lastWasPeriod = false; 257 while (pos < length 258 && ((c = valueStr.charAt(pos)) != ' ') 259 && ((c = valueStr.charAt(pos)) != ')')) 260 { 261 if (c == '.') 262 { 263 if (lastWasPeriod) 264 { 265 LocalizableMessage message = 266 ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID. 267 get(valueStr, pos - 1); 268 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 269 message); 270 } 271 else 272 { 273 lastWasPeriod = true; 274 } 275 } 276 else if (! isDigit(c)) 277 { 278 // This must have been an illegal character. 279 LocalizableMessage message = 280 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1); 281 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 282 } 283 else 284 { 285 lastWasPeriod = false; 286 } 287 pos++; 288 } 289 } 290 else 291 { 292 // This must be a "fake" OID. In this case, we will only accept 293 // alphabetic characters, numeric digits, and the hyphen. 294 while (pos < length 295 && ((c = valueStr.charAt(pos)) != ' ') 296 && ((c = valueStr.charAt(pos)) != ')')) 297 { 298 if (isAlpha(c) 299 || isDigit(c) 300 || c == '-' 301 || (c == '_' && DirectoryServer.allowAttributeNameExceptions())) 302 { 303 // This is fine. It is an acceptable character. 304 pos++; 305 } 306 else 307 { 308 // This must have been an illegal character. 309 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.get(valueStr, c, pos - 1); 310 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 311 } 312 } 313 } 314 315 316 // If we're at the end of the value, then it isn't a valid attribute type 317 // description. Otherwise, parse out the OID. 318 String oid; 319 if (pos >= length) 320 { 321 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 322 throw new DirectoryException( 323 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 324 } 325 else 326 { 327 oid = lowerStr.substring(oidStartPos, pos); 328 } 329 330 331 // Skip over the space(s) after the OID. 332 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 333 { 334 pos++; 335 } 336 337 if (pos >= length) 338 { 339 // This means that the end of the value was reached before we could find 340 // the OID. Ths is illegal. 341 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 342 throw new DirectoryException( 343 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 344 } 345 346 347 // At this point, we should have a pretty specific syntax that describes 348 // what may come next, but some of the components are optional and it would 349 // be pretty easy to put something in the wrong order, so we will be very 350 // flexible about what we can accept. Just look at the next token, figure 351 // out what it is and how to treat what comes after it, then repeat until 352 // we get to the end of the value. But before we start, set default values 353 // for everything else we might need to know. 354 String primaryName = oid; 355 List<String> typeNames = new LinkedList<>(); 356 String description = null; 357 AttributeType superiorType = null; 358 Syntax syntax = DirectoryServer.getDefaultAttributeSyntax(); 359 MatchingRule approximateMatchingRule = null; 360 MatchingRule equalityMatchingRule = null; 361 MatchingRule orderingMatchingRule = null; 362 MatchingRule substringMatchingRule = null; 363 AttributeUsage attributeUsage = AttributeUsage.USER_APPLICATIONS; 364 boolean isCollective = false; 365 boolean isNoUserModification = false; 366 boolean isObsolete = false; 367 boolean isSingleValue = false; 368 HashMap<String,List<String>> extraProperties = new LinkedHashMap<>(); 369 370 371 while (true) 372 { 373 StringBuilder tokenNameBuffer = new StringBuilder(); 374 pos = readTokenName(valueStr, tokenNameBuffer, pos); 375 String tokenName = tokenNameBuffer.toString(); 376 String lowerTokenName = toLowerCase(tokenName); 377 if (")".equals(tokenName)) 378 { 379 // We must be at the end of the value. If not, then that's a problem. 380 if (pos < length) 381 { 382 LocalizableMessage message = 383 ERR_ATTR_SYNTAX_ATTRTYPE_UNEXPECTED_CLOSE_PARENTHESIS. 384 get(valueStr, pos - 1); 385 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 386 message); 387 } 388 389 break; 390 } 391 else if ("name".equals(lowerTokenName)) 392 { 393 // This specifies the set of names for the attribute type. It may be a 394 // single name in single quotes, or it may be an open parenthesis 395 // followed by one or more names in single quotes separated by spaces. 396 c = valueStr.charAt(pos++); 397 if (c == '\'') 398 { 399 StringBuilder userBuffer = new StringBuilder(); 400 StringBuilder lowerBuffer = new StringBuilder(); 401 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos - 1); 402 primaryName = userBuffer.toString(); 403 typeNames.add(primaryName); 404 } 405 else if (c == '(') 406 { 407 StringBuilder userBuffer = new StringBuilder(); 408 StringBuilder lowerBuffer = new StringBuilder(); 409 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 410 pos); 411 primaryName = userBuffer.toString(); 412 typeNames.add(primaryName); 413 414 415 while (true) 416 { 417 if (valueStr.charAt(pos) == ')') 418 { 419 // Skip over any spaces after the parenthesis. 420 pos++; 421 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 422 { 423 pos++; 424 } 425 426 break; 427 } 428 userBuffer = new StringBuilder(); 429 lowerBuffer = new StringBuilder(); 430 431 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos); 432 typeNames.add(userBuffer.toString()); 433 } 434 } 435 else 436 { 437 // This is an illegal character. 438 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(valueStr, c, pos - 1); 439 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 440 } 441 //RFC 2251: A specification may also assign one or more textual names 442 //for an attribute type. These names MUST begin with a letter, and 443 //only contain ASCII letters, digit characters and hyphens. 444 445 //The global config hasn't been read so far. Allow the name exceptions 446 //during startup. 447 boolean allowExceptions = DirectoryServer.isRunning()? 448 DirectoryServer.allowAttributeNameExceptions():true; 449 //Iterate over all the names and throw an exception if it is invalid. 450 for(String name : typeNames) 451 { 452 for(int index=0; index < name.length(); index++) 453 { 454 char ch = name.charAt(index); 455 switch(ch) 456 { 457 case '-': 458 //hyphen is allowed but not as the first byte. 459 if (index==0) 460 { 461 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 462 ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_DASH.get(value)); 463 } 464 break; 465 case '_': 466 // This will never be allowed as the first character. It 467 // may be allowed for subsequent characters if the attribute 468 // name exceptions option is enabled. 469 if (index==0) 470 { 471 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 472 ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_UNDERSCORE.get( 473 value, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS)); 474 } 475 else if (!allowExceptions) 476 { 477 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 478 ERR_ATTR_SYNTAX_ATTR_ILLEGAL_UNDERSCORE_CHAR.get( 479 value, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS)); 480 } 481 break; 482 483 default: 484 //Only digits and ascii letters are allowed but the first byte 485 //can not be a digit. 486 if(index ==0 && isDigit(ch) && !allowExceptions) 487 { 488 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 489 ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_DIGIT.get( 490 value, ch, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS)); 491 } 492 else if (!(('0'<=ch && ch<='9') 493 || ('A'<=ch && ch<='Z') 494 || ('a'<=ch && ch<='z'))) 495 { 496 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 497 ERR_ATTR_SYNTAX_ATTR_ILLEGAL_CHAR.get(value, ch, index)); 498 } 499 break; 500 } 501 } 502 503 } 504 505 } 506 else if ("desc".equals(lowerTokenName)) 507 { 508 // This specifies the description for the attribute type. It is an 509 // arbitrary string of characters enclosed in single quotes. 510 StringBuilder descriptionBuffer = new StringBuilder(); 511 pos = readQuotedString(valueStr, descriptionBuffer, pos); 512 description = descriptionBuffer.toString(); 513 } 514 else if ("obsolete".equals(lowerTokenName)) 515 { 516 // This indicates whether the attribute type should be considered 517 // obsolete. We do not need to do any more parsing for this token. 518 isObsolete = true; 519 } 520 else if ("sup".equals(lowerTokenName)) 521 { 522 // This specifies the name or OID of the superior attribute type from 523 // which this attribute type should inherit its properties. 524 StringBuilder woidBuffer = new StringBuilder(); 525 pos = readWOID(lowerStr, woidBuffer, pos); 526 String woidString = woidBuffer.toString(); 527 superiorType = schema.getAttributeType(woidString); 528 if (superiorType == null) 529 { 530 if (allowUnknownElements) 531 { 532 superiorType = DirectoryServer.getDefaultAttributeType(woidString); 533 } 534 else 535 { 536 // This is bad because we don't know what the superior attribute 537 // type is so we can't base this attribute type on it. 538 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE.get(oid, woidString); 539 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 540 } 541 } 542 543 544 // Use the information in the superior type to provide defaults for the 545 // rest of the components in this attribute type description. 546 // Technically, the definition of the superior type should be provided 547 // before the matching rule, syntax, single-value, collective, 548 // no-user-modification, and usage components, and in that case we won't 549 // undo something else that has already been set by an earlier 550 // definition. However, if the information is provided out-of-order, 551 // then it is possible that this could overwrite some desired setting 552 // that is different from that of the supertype. 553 approximateMatchingRule = superiorType.getApproximateMatchingRule(); 554 equalityMatchingRule = superiorType.getEqualityMatchingRule(); 555 orderingMatchingRule = superiorType.getOrderingMatchingRule(); 556 substringMatchingRule = superiorType.getSubstringMatchingRule(); 557 syntax = superiorType.getSyntax(); 558 isSingleValue = superiorType.isSingleValue(); 559 isCollective = superiorType.isCollective(); 560 isNoUserModification = superiorType.isNoUserModification(); 561 attributeUsage = superiorType.getUsage(); 562 } 563 else if ("equality".equals(lowerTokenName)) 564 { 565 // This specifies the name or OID of the equality matching rule to use 566 // for this attribute type. 567 StringBuilder woidBuffer = new StringBuilder(); 568 pos = readWOID(lowerStr, woidBuffer, pos); 569 MatchingRule emr = 570 schema.getMatchingRule(woidBuffer.toString()); 571 if (emr == null) 572 { 573 // This is bad because we have no idea what the equality matching 574 // rule should be. 575 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR.get(oid, woidBuffer); 576 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 577 } 578 else 579 { 580 equalityMatchingRule = emr; 581 } 582 } 583 else if ("ordering".equals(lowerTokenName)) 584 { 585 // This specifies the name or OID of the ordering matching rule to use 586 // for this attribute type. 587 StringBuilder woidBuffer = new StringBuilder(); 588 pos = readWOID(lowerStr, woidBuffer, pos); 589 MatchingRule omr = schema.getMatchingRule(woidBuffer.toString()); 590 if (omr == null) 591 { 592 // This is bad because we have no idea what the ordering matching 593 // rule should be. 594 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR.get(oid, woidBuffer); 595 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 596 } 597 else 598 { 599 orderingMatchingRule = omr; 600 } 601 } 602 else if ("substr".equals(lowerTokenName)) 603 { 604 // This specifies the name or OID of the substring matching rule to use 605 // for this attribute type. 606 StringBuilder woidBuffer = new StringBuilder(); 607 pos = readWOID(lowerStr, woidBuffer, pos); 608 MatchingRule smr = schema.getMatchingRule(woidBuffer.toString()); 609 if (smr == null) 610 { 611 // This is bad because we have no idea what the substring matching 612 // rule should be. 613 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR.get(oid, woidBuffer); 614 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 615 } 616 else 617 { 618 substringMatchingRule = smr; 619 } 620 } 621 else if ("syntax".equals(lowerTokenName)) 622 { 623 // This specifies the numeric OID of the syntax for this matching rule. 624 // It may optionally be immediately followed by an open curly brace, an 625 // integer value, and a close curly brace to suggest the minimum number 626 // of characters that should be allowed in values of that type. This 627 // implementation will ignore any such length because it does not 628 // impose any practical limit on the length of attribute values. 629 boolean inBrace = false; 630 boolean lastWasPeriod = false; 631 StringBuilder oidBuffer = new StringBuilder(); 632 while (pos < length) 633 { 634 c = lowerStr.charAt(pos++); 635 if (inBrace) 636 { 637 // The only thing we'll allow here will be numeric digits and the 638 // closing curly brace. 639 if (c == '}') 640 { 641 // The next character must be a space or a closing parenthesis. 642 c = lowerStr.charAt(pos); 643 if (c != ' ' && c != ')') 644 { 645 LocalizableMessage message = 646 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1); 647 throw new DirectoryException( 648 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 649 } 650 651 break; 652 } 653 else if (! isDigit(c)) 654 { 655 LocalizableMessage message = 656 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1); 657 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 658 message); 659 } 660 } 661 else 662 { 663 if (isDigit(c)) 664 { 665 oidBuffer.append(c); 666 lastWasPeriod = false; 667 } 668 else if (c == '.') 669 { 670 if (lastWasPeriod) 671 { 672 LocalizableMessage message = 673 ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID. 674 get(valueStr, pos - 1); 675 throw new DirectoryException( 676 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 677 } 678 else 679 { 680 oidBuffer.append(c); 681 lastWasPeriod = true; 682 } 683 } 684 else if (c == '{') 685 { 686 // It's the start of the length specification. 687 inBrace = true; 688 } 689 else if (c == ' ') 690 { 691 // It's the end of the value. 692 break; 693 } 694 else if(c == ')') 695 { 696 // As per RFC 4512 (4.1.2) it is end of the value. 697 --pos; 698 break; 699 } 700 else 701 { 702 LocalizableMessage message = 703 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1); 704 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 705 message); 706 } 707 } 708 } 709 710 syntax = schema.getSyntax(oidBuffer.toString()); 711 if (syntax == null) 712 { 713 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SYNTAX.get(oid, oidBuffer); 714 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 715 message); 716 } 717 718 if (approximateMatchingRule == null) 719 { 720 approximateMatchingRule = syntax.getApproximateMatchingRule(); 721 } 722 723 if (equalityMatchingRule == null) 724 { 725 equalityMatchingRule = syntax.getEqualityMatchingRule(); 726 } 727 728 if (orderingMatchingRule == null) 729 { 730 orderingMatchingRule = syntax.getOrderingMatchingRule(); 731 } 732 733 if (substringMatchingRule == null) 734 { 735 substringMatchingRule = syntax.getSubstringMatchingRule(); 736 } 737 } 738 else if ("single-value".equals(lowerTokenName)) 739 { 740 // This indicates that attributes of this type are allowed to have at 741 // most one value. We do not need any more parsing for this token. 742 isSingleValue = true; 743 } 744 else if ("collective".equals(lowerTokenName)) 745 { 746 // This indicates that attributes of this type are collective (i.e., 747 // have their values generated dynamically in some way). We do not need 748 // any more parsing for this token. 749 isCollective = true; 750 } 751 else if ("no-user-modification".equals(lowerTokenName)) 752 { 753 // This indicates that the values of attributes of this type are not to 754 // be modified by end users. We do not need any more parsing for this 755 // token. 756 isNoUserModification = true; 757 } 758 else if ("usage".equals(lowerTokenName)) 759 { 760 // This specifies the usage string for this attribute type. It should 761 // be followed by one of the strings "userApplications", 762 // "directoryOperation", "distributedOperation", or "dSAOperation". 763 StringBuilder usageBuffer = new StringBuilder(); 764 while (pos < length) 765 { 766 c = lowerStr.charAt(pos++); 767 if (c == ' ') 768 { 769 break; 770 } 771 else if(c == ')') 772 { 773 pos--; 774 break; 775 } 776 else 777 { 778 usageBuffer.append(c); 779 } 780 } 781 782 String usageStr = usageBuffer.toString(); 783 if ("userapplications".equals(usageStr)) 784 { 785 attributeUsage = AttributeUsage.USER_APPLICATIONS; 786 } 787 else if ("directoryoperation".equals(usageStr)) 788 { 789 attributeUsage = AttributeUsage.DIRECTORY_OPERATION; 790 } 791 else if ("distributedoperation".equals(usageStr)) 792 { 793 attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION; 794 } 795 else if ("dsaoperation".equals(usageStr)) 796 { 797 attributeUsage = AttributeUsage.DSA_OPERATION; 798 } 799 else 800 { 801 // This must be an illegal usage. 802 attributeUsage = AttributeUsage.USER_APPLICATIONS; 803 804 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(oid, usageStr); 805 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 806 } 807 } 808 else 809 { 810 // This must be a non-standard property and it must be followed by 811 // either a single value in single quotes or an open parenthesis 812 // followed by one or more values in single quotes separated by spaces 813 // followed by a close parenthesis. 814 List<String> valueList = new ArrayList<>(); 815 pos = readExtraParameterValues(valueStr, valueList, pos); 816 extraProperties.put(tokenName, valueList); 817 } 818 } 819 820 List<String> approxRules = extraProperties.get(SCHEMA_PROPERTY_APPROX_RULE); 821 if (approxRules != null && !approxRules.isEmpty()) 822 { 823 String ruleName = approxRules.get(0); 824 String lowerName = toLowerCase(ruleName); 825 MatchingRule amr = schema.getMatchingRule(lowerName); 826 if (amr == null) 827 { 828 // This is bad because we have no idea what the approximate matching 829 // rule should be. 830 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR.get(oid, ruleName); 831 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 832 } 833 else 834 { 835 approximateMatchingRule = amr; 836 } 837 } 838 839 840 // If there is a superior type, then it must have the same usage as the 841 // subordinate type. Also, if the superior type is collective, then so must 842 // the subordinate type be collective. 843 if (superiorType != null) 844 { 845 if (superiorType.getUsage() != attributeUsage) 846 { 847 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE.get( 848 oid, attributeUsage, superiorType.getNameOrOID()); 849 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 850 } 851 852 if (superiorType.isCollective() && !isCollective) 853 { 854 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 855 WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE 856 .get(oid, superiorType.getNameOrOID())); 857 } 858 } 859 860 861 // If the attribute type is NO-USER-MODIFICATION, then it must not have a 862 // usage of userApplications. 863 if (isNoUserModification 864 && attributeUsage == AttributeUsage.USER_APPLICATIONS) 865 { 866 LocalizableMessage message = 867 WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL.get(oid); 868 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 869 } 870 871 CommonSchemaElements.checkSafeProperties(extraProperties); 872 873 return new AttributeType(value.toString(), primaryName, typeNames, oid, 874 description, superiorType, syntax, 875 approximateMatchingRule, equalityMatchingRule, 876 orderingMatchingRule, substringMatchingRule, 877 attributeUsage, isCollective, isNoUserModification, 878 isObsolete, isSingleValue, extraProperties); 879 } 880 881 882 883 /** 884 * Reads the next token name from the attribute type definition, skipping over 885 * any leading or trailing spaces, and appends it to the provided buffer. 886 * 887 * @param valueStr The string representation of the attribute type 888 * definition. 889 * @param tokenName The buffer into which the token name will be written. 890 * @param startPos The position in the provided string at which to start 891 * reading the token name. 892 * 893 * @return The position of the first character that is not part of the token 894 * name or one of the trailing spaces after it. 895 * 896 * @throws DirectoryException If a problem is encountered while reading the 897 * token name. 898 */ 899 private static int readTokenName(String valueStr, StringBuilder tokenName, 900 int startPos) 901 throws DirectoryException 902 { 903 // Skip over any spaces at the beginning of the value. 904 char c = '\u0000'; 905 int length = valueStr.length(); 906 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 907 { 908 startPos++; 909 } 910 911 if (startPos >= length) 912 { 913 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 914 throw new DirectoryException( 915 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 916 } 917 918 919 // Read until we find the next space. 920 while (startPos < length 921 && ((c = valueStr.charAt(startPos)) != ' ') 922 && ((c = valueStr.charAt(startPos)) != ')')) 923 { 924 tokenName.append(c); 925 startPos++; 926 } 927 928 //We may be left with only ')' which is not part of the token yet. 929 //Let us see if it is the case. 930 if(tokenName.length()==0 && c == ')') 931 { 932 tokenName.append(c); 933 startPos++; 934 } 935 936 // Skip over any trailing spaces after the value. 937 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 938 { 939 startPos++; 940 } 941 942 943 // Return the position of the first non-space character after the token. 944 return startPos; 945 } 946 947 948 949 /** 950 * Reads the value of a string enclosed in single quotes, skipping over the 951 * quotes and any leading or trailing spaces, and appending the string to the 952 * provided buffer. 953 * 954 * @param valueStr The user-provided representation of the attribute type 955 * definition. 956 * @param valueBuffer The buffer into which the user-provided representation 957 * of the value will be placed. 958 * @param startPos The position in the provided string at which to start 959 * reading the quoted string. 960 * 961 * @return The position of the first character that is not part of the quoted 962 * string or one of the trailing spaces after it. 963 * 964 * @throws DirectoryException If a problem is encountered while reading the 965 * quoted string. 966 */ 967 private static int readQuotedString(String valueStr, 968 StringBuilder valueBuffer, int startPos) 969 throws DirectoryException 970 { 971 // Skip over any spaces at the beginning of the value. 972 char c = '\u0000'; 973 int length = valueStr.length(); 974 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 975 { 976 startPos++; 977 } 978 979 if (startPos >= length) 980 { 981 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 982 throw new DirectoryException( 983 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 984 } 985 986 987 // The next character must be a single quote. 988 if (c != '\'') 989 { 990 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c); 991 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 992 } 993 994 995 // Read until we find the closing quote. 996 startPos++; 997 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 998 { 999 valueBuffer.append(c); 1000 startPos++; 1001 } 1002 1003 1004 // Skip over any trailing spaces after the value. 1005 startPos++; 1006 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 1007 { 1008 startPos++; 1009 } 1010 1011 1012 // If we're at the end of the value, then that's illegal. 1013 if (startPos >= length) 1014 { 1015 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 1016 throw new DirectoryException( 1017 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1018 } 1019 1020 1021 // Return the position of the first non-space character after the token. 1022 return startPos; 1023 } 1024 1025 1026 1027 /** 1028 * Reads the value of a string enclosed in single quotes, skipping over the 1029 * quotes and any leading or trailing spaces, and appending the string to the 1030 * provided buffer. 1031 * 1032 * @param valueStr The user-provided representation of the attribute type 1033 * definition. 1034 * @param lowerStr The all-lowercase representation of the attribute type 1035 * definition. 1036 * @param userBuffer The buffer into which the user-provided representation 1037 * of the value will be placed. 1038 * @param lowerBuffer The buffer into which the all-lowercase representation 1039 * of the value will be placed. 1040 * @param startPos The position in the provided string at which to start 1041 * reading the quoted string. 1042 * 1043 * @return The position of the first character that is not part of the quoted 1044 * string or one of the trailing spaces after it. 1045 * 1046 * @throws DirectoryException If a problem is encountered while reading the 1047 * quoted string. 1048 */ 1049 private static int readQuotedString(String valueStr, String lowerStr, 1050 StringBuilder userBuffer, 1051 StringBuilder lowerBuffer, int startPos) 1052 throws DirectoryException 1053 { 1054 // Skip over any spaces at the beginning of the value. 1055 char c = '\u0000'; 1056 int length = lowerStr.length(); 1057 while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' ')) 1058 { 1059 startPos++; 1060 } 1061 1062 if (startPos >= length) 1063 { 1064 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr); 1065 throw new DirectoryException( 1066 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1067 } 1068 1069 1070 // The next character must be a single quote. 1071 if (c != '\'') 1072 { 1073 LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c); 1074 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1075 } 1076 1077 1078 // Read until we find the closing quote. 1079 startPos++; 1080 while (startPos < length && ((c = lowerStr.charAt(startPos)) != '\'')) 1081 { 1082 lowerBuffer.append(c); 1083 userBuffer.append(valueStr.charAt(startPos)); 1084 startPos++; 1085 } 1086 1087 1088 // Skip over any trailing spaces after the value. 1089 startPos++; 1090 while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' ')) 1091 { 1092 startPos++; 1093 } 1094 1095 1096 // If we're at the end of the value, then that's illegal. 1097 if (startPos >= length) 1098 { 1099 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr); 1100 throw new DirectoryException( 1101 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1102 } 1103 1104 1105 // Return the position of the first non-space character after the token. 1106 return startPos; 1107 } 1108 1109 1110 1111 /** 1112 * Reads the attribute description or numeric OID from the provided string, 1113 * skipping over any leading or trailing spaces, and appending the value to 1114 * the provided buffer. 1115 * 1116 * @param lowerStr The string from which the name or OID is to be read. 1117 * @param woidBuffer The buffer into which the name or OID should be 1118 * appended. 1119 * @param startPos The position at which to start reading. 1120 * 1121 * @return The position of the first character after the name or OID that is 1122 * not a space. 1123 * 1124 * @throws DirectoryException If a problem is encountered while reading the 1125 * name or OID. 1126 */ 1127 private static int readWOID(String lowerStr, StringBuilder woidBuffer, 1128 int startPos) 1129 throws DirectoryException 1130 { 1131 // Skip over any spaces at the beginning of the value. 1132 char c = '\u0000'; 1133 int length = lowerStr.length(); 1134 while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' ')) 1135 { 1136 startPos++; 1137 } 1138 1139 if (startPos >= length) 1140 { 1141 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr); 1142 throw new DirectoryException( 1143 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1144 } 1145 1146 1147 // The next character must be either numeric (for an OID) or alphabetic (for 1148 // an attribute description). 1149 if (isDigit(c)) 1150 { 1151 // This must be a numeric OID. In that case, we will accept only digits 1152 // and periods, but not consecutive periods. 1153 boolean lastWasPeriod = false; 1154 while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' ')) 1155 { 1156 if (c == '.') 1157 { 1158 if (lastWasPeriod) 1159 { 1160 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1161 ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID 1162 .get(lowerStr, startPos - 1)); 1163 } 1164 else 1165 { 1166 woidBuffer.append(c); 1167 lastWasPeriod = true; 1168 } 1169 } 1170 else if (! isDigit(c)) 1171 { 1172 // Technically, this must be an illegal character. However, it is 1173 // possible that someone just got sloppy and did not include a space 1174 // between the name/OID and a closing parenthesis. In that case, 1175 // we'll assume it's the end of the value. What's more, we'll have 1176 // to prematurely return to nasty side effects from stripping off 1177 // additional characters. 1178 if (c == ')') 1179 { 1180 return startPos - 1; 1181 } 1182 1183 // This must have been an illegal character. 1184 LocalizableMessage message = 1185 ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(lowerStr, c, startPos-1); 1186 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1187 } 1188 else 1189 { 1190 woidBuffer.append(c); 1191 lastWasPeriod = false; 1192 } 1193 } 1194 } 1195 else if (isAlpha(c)) 1196 { 1197 // This must be an attribute description. In this case, we will only 1198 // accept alphabetic characters, numeric digits, and the hyphen. 1199 while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' ')) 1200 { 1201 if (isAlpha(c) 1202 || isDigit(c) 1203 || c == '-' 1204 || (c == '_' && DirectoryServer.allowAttributeNameExceptions())) 1205 { 1206 woidBuffer.append(c); 1207 } 1208 else 1209 { 1210 // Technically, this must be an illegal character. However, it is 1211 // possible that someone just got sloppy and did not include a space 1212 // between the name/OID and a closing parenthesis. In that case, 1213 // we'll assume it's the end of the value. What's more, we'll have 1214 // to prematurely return to nasty side effects from stripping off 1215 // additional characters. 1216 if (c == ')') 1217 { 1218 return startPos - 1; 1219 } 1220 1221 // This must have been an illegal character. 1222 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.get( 1223 lowerStr, c, startPos - 1); 1224 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1225 } 1226 } 1227 } 1228 else 1229 { 1230 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(lowerStr, c, startPos); 1231 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1232 } 1233 1234 1235 // Skip over any trailing spaces after the value. 1236 while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' ')) 1237 { 1238 startPos++; 1239 } 1240 1241 1242 // If we're at the end of the value, then that's illegal. 1243 if (startPos >= length) 1244 { 1245 LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr); 1246 throw new DirectoryException( 1247 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1248 } 1249 1250 1251 // Return the position of the first non-space character after the token. 1252 return startPos; 1253 } 1254 1255 1256 1257 /** 1258 * Reads the value for an "extra" parameter. It will handle a single unquoted 1259 * word (which is technically illegal, but we'll allow it), a single quoted 1260 * string, or an open parenthesis followed by a space-delimited set of quoted 1261 * strings or unquoted words followed by a close parenthesis. 1262 * 1263 * @param valueStr The string containing the information to be read. 1264 * @param valueList The list of "extra" parameter values read so far. 1265 * @param startPos The position in the value string at which to start 1266 * reading. 1267 * 1268 * @return The "extra" parameter value that was read. 1269 * 1270 * @throws DirectoryException If a problem occurs while attempting to read 1271 * the value. 1272 */ 1273 private static int readExtraParameterValues(String valueStr, 1274 List<String> valueList, int startPos) 1275 throws DirectoryException 1276 { 1277 // Skip over any leading spaces. 1278 int length = valueStr.length(); 1279 char c = '\u0000'; 1280 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 1281 { 1282 startPos++; 1283 } 1284 1285 if (startPos >= length) 1286 { 1287 LocalizableMessage message = 1288 ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 1289 throw new DirectoryException( 1290 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1291 } 1292 1293 1294 // Look at the next character. If it is a quote, then parse until the next 1295 // quote and end. If it is an open parenthesis, then parse individual 1296 // values until the close parenthesis and end. Otherwise, parse until the 1297 // next space and end. 1298 if (c == '\'') 1299 { 1300 // Parse until the closing quote. 1301 StringBuilder valueBuffer = new StringBuilder(); 1302 startPos++; 1303 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 1304 { 1305 valueBuffer.append(c); 1306 startPos++; 1307 } 1308 startPos++; 1309 valueList.add(valueBuffer.toString()); 1310 } 1311 else if (c == '(') 1312 { 1313 startPos++; 1314 // We're expecting a list of values. Quoted, space separated. 1315 while (true) 1316 { 1317 // Skip over any leading spaces; 1318 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 1319 { 1320 startPos++; 1321 } 1322 1323 if (startPos >= length) 1324 { 1325 LocalizableMessage message = 1326 ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 1327 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1328 message); 1329 } 1330 1331 if (c == ')') 1332 { 1333 // This is the end of the list. 1334 startPos++; 1335 break; 1336 } 1337 else if (c == '(') 1338 { 1339 // This is an illegal character. 1340 LocalizableMessage message = 1341 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXTENSION_INVALID_CHARACTER.get( 1342 valueStr, startPos); 1343 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1344 message); 1345 } 1346 else if (c == '\'') 1347 { 1348 // We have a quoted string 1349 StringBuilder valueBuffer = new StringBuilder(); 1350 startPos++; 1351 while (startPos < length 1352 && ((c = valueStr.charAt(startPos)) != '\'')) 1353 { 1354 valueBuffer.append(c); 1355 startPos++; 1356 } 1357 1358 valueList.add(valueBuffer.toString()); 1359 startPos++; 1360 } 1361 else 1362 { 1363 //Consider unquoted string 1364 StringBuilder valueBuffer = new StringBuilder(); 1365 while (startPos < length 1366 && ((c = valueStr.charAt(startPos)) != ' ')) 1367 { 1368 valueBuffer.append(c); 1369 startPos++; 1370 } 1371 1372 valueList.add(valueBuffer.toString()); 1373 } 1374 1375 if (startPos >= length) 1376 { 1377 LocalizableMessage message = 1378 ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 1379 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1380 message); 1381 } 1382 } 1383 } 1384 else 1385 { 1386 // Parse until the next space. 1387 StringBuilder valueBuffer = new StringBuilder(); 1388 while (startPos < length && ((c = valueStr.charAt(startPos)) != ' ')) 1389 { 1390 valueBuffer.append(c); 1391 startPos++; 1392 } 1393 1394 valueList.add(valueBuffer.toString()); 1395 } 1396 1397 // Skip over any trailing spaces. 1398 while (startPos < length && valueStr.charAt(startPos) == ' ') 1399 { 1400 startPos++; 1401 } 1402 1403 if (startPos >= length) 1404 { 1405 LocalizableMessage message = 1406 ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr); 1407 throw new DirectoryException( 1408 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1409 } 1410 1411 return startPos; 1412 } 1413 1414 1415 1416 /** {@inheritDoc} */ 1417 @Override 1418 public ConfigChangeResult applyConfigurationChange( 1419 AttributeTypeDescriptionAttributeSyntaxCfg configuration) 1420 { 1421 currentConfig = configuration; 1422 stripMinimumUpperBound = configuration.isStripSyntaxMinUpperBound(); 1423 updateNewSchema(); 1424 return new ConfigChangeResult(); 1425 } 1426 1427 1428 1429 /** {@inheritDoc} */ 1430 @Override 1431 public boolean isConfigurationChangeAcceptable( 1432 AttributeTypeDescriptionAttributeSyntaxCfg configuration, 1433 List<LocalizableMessage> unacceptableReasons) 1434 { 1435 // The configuration will always be acceptable. 1436 return true; 1437 } 1438 1439 /** 1440 * Boolean that indicates that the minimum upper bound value should be 1441 * stripped from the Attribute Type Syntax Description. 1442 * 1443 * @return True if the minimum upper bound value should be stripped. 1444 */ 1445 public static boolean isStripSyntaxMinimumUpperBound() { 1446 return stripMinimumUpperBound; 1447 } 1448}