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