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