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.ByteString;
046    import org.opends.server.types.DirectoryException;
047    import org.opends.server.types.DITStructureRule;
048    import org.opends.server.types.InitializationException;
049    import org.opends.server.types.NameForm;
050    import org.opends.server.types.ResultCode;
051    import org.opends.server.types.Schema;
052    
053    import static org.opends.server.loggers.debug.DebugLogger.*;
054    import org.opends.server.loggers.debug.DebugTracer;
055    import org.opends.server.types.DebugLogLevel;
056    import static org.opends.messages.SchemaMessages.*;
057    import org.opends.messages.MessageBuilder;
058    import static org.opends.server.schema.SchemaConstants.*;
059    import static org.opends.server.util.StaticUtils.*;
060    
061    
062    
063    /**
064     * This class implements the DIT structure rule description syntax, which is
065     * used to hold DIT structure rule definitions in the server schema.  The format
066     * of this syntax is defined in RFC 2252.
067     */
068    public class DITStructureRuleSyntax
069           extends AttributeSyntax<AttributeSyntaxCfg>
070    {
071      /**
072       * The tracer object for the debug logger.
073       */
074      private static final DebugTracer TRACER = getTracer();
075    
076    
077    
078    
079      // The default equality matching rule for this syntax.
080      private EqualityMatchingRule defaultEqualityMatchingRule;
081    
082      // The default ordering matching rule for this syntax.
083      private OrderingMatchingRule defaultOrderingMatchingRule;
084    
085      // The default substring matching rule for this syntax.
086      private SubstringMatchingRule defaultSubstringMatchingRule;
087    
088    
089    
090      /**
091       * Creates a new instance of this syntax.  Note that the only thing that
092       * should be done here is to invoke the default constructor for the
093       * superclass.  All initialization should be performed in the
094       * <CODE>initializeSyntax</CODE> method.
095       */
096      public DITStructureRuleSyntax()
097      {
098        super();
099      }
100    
101    
102    
103      /**
104       * {@inheritDoc}
105       */
106      public void initializeSyntax(AttributeSyntaxCfg configuration)
107             throws ConfigException, InitializationException
108      {
109        defaultEqualityMatchingRule =
110             DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID);
111        if (defaultEqualityMatchingRule == null)
112        {
113          Message message = ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
114              EMR_CASE_IGNORE_OID, SYNTAX_DIT_STRUCTURE_RULE_NAME);
115          throw new InitializationException(message);
116        }
117    
118        defaultOrderingMatchingRule =
119             DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID);
120        if (defaultOrderingMatchingRule == null)
121        {
122          Message message = ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
123              OMR_CASE_IGNORE_OID, SYNTAX_DIT_STRUCTURE_RULE_NAME);
124          throw new InitializationException(message);
125        }
126    
127        defaultSubstringMatchingRule =
128             DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
129        if (defaultSubstringMatchingRule == null)
130        {
131          Message message = ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
132              SMR_CASE_IGNORE_OID, SYNTAX_DIT_STRUCTURE_RULE_NAME);
133          throw new InitializationException(message);
134        }
135      }
136    
137    
138    
139      /**
140       * {@inheritDoc}
141       */
142      public String getSyntaxName()
143      {
144        return SYNTAX_DIT_STRUCTURE_RULE_NAME;
145      }
146    
147    
148    
149      /**
150       * {@inheritDoc}
151       */
152      public String getOID()
153      {
154        return SYNTAX_DIT_STRUCTURE_RULE_OID;
155      }
156    
157    
158    
159      /**
160       * {@inheritDoc}
161       */
162      public String getDescription()
163      {
164        return SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION;
165      }
166    
167    
168    
169      /**
170       * {@inheritDoc}
171       */
172      public EqualityMatchingRule getEqualityMatchingRule()
173      {
174        return defaultEqualityMatchingRule;
175      }
176    
177    
178    
179      /**
180       * {@inheritDoc}
181       */
182      public OrderingMatchingRule getOrderingMatchingRule()
183      {
184        return defaultOrderingMatchingRule;
185      }
186    
187    
188    
189      /**
190       * {@inheritDoc}
191       */
192      public SubstringMatchingRule getSubstringMatchingRule()
193      {
194        return defaultSubstringMatchingRule;
195      }
196    
197    
198    
199      /**
200       * {@inheritDoc}
201       */
202      public ApproximateMatchingRule getApproximateMatchingRule()
203      {
204        // There is no approximate matching rule by default.
205        return null;
206      }
207    
208    
209    
210      /**
211       * {@inheritDoc}
212       */
213      public boolean valueIsAcceptable(ByteString value,
214                                       MessageBuilder invalidReason)
215      {
216        // We'll use the decodeDITStructureRule method to determine if the value is
217        // acceptable.
218        try
219        {
220          decodeDITStructureRule(value, DirectoryServer.getSchema(), true);
221          return true;
222        }
223        catch (DirectoryException de)
224        {
225          if (debugEnabled())
226          {
227            TRACER.debugCaught(DebugLogLevel.ERROR, de);
228          }
229    
230          invalidReason.append(de.getMessageObject());
231          return false;
232        }
233      }
234    
235    
236    
237      /**
238       * Decodes the contents of the provided ASN.1 octet string as a DIT structure
239       * rule definition according to the rules of this syntax.  Note that the
240       * provided octet string value does not need to be normalized (and in fact, it
241       * should not be in order to allow the desired capitalization to be
242       * preserved).
243       *
244       * @param  value                 The ASN.1 octet string containing the value
245       *                               to decode (it does not need to be
246       *                               normalized).
247       * @param  schema                The schema to use to resolve references to
248       *                               other schema elements.
249       * @param  allowUnknownElements  Indicates whether to allow values that
250       *                               reference a name form and/or superior rules
251       *                               which are not defined in the server schema.
252       *                               This should only be true when called by
253       *                               {@code valueIsAcceptable}.
254       *
255       * @return  The decoded DIT structure rule definition.
256       *
257       * @throws  DirectoryException  If the provided value cannot be decoded as an
258       *                              DIT structure rule definition.
259       */
260      public static DITStructureRule decodeDITStructureRule(ByteString value,
261                                          Schema schema,
262                                          boolean allowUnknownElements)
263             throws DirectoryException
264      {
265        // Get string representations of the provided value using the provided form
266        // and with all lowercase characters.
267        String valueStr = value.stringValue();
268        String lowerStr = toLowerCase(valueStr);
269    
270    
271        // We'll do this a character at a time.  First, skip over any leading
272        // whitespace.
273        int pos    = 0;
274        int length = valueStr.length();
275        while ((pos < length) && (valueStr.charAt(pos) == ' '))
276        {
277          pos++;
278        }
279    
280        if (pos >= length)
281        {
282          // This means that the value was empty or contained only whitespace.  That
283          // is illegal.
284          Message message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
285          throw new DirectoryException(
286                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
287        }
288    
289    
290        // The next character must be an open parenthesis.  If it is not, then that
291        // is an error.
292        char c = valueStr.charAt(pos++);
293        if (c != '(')
294        {
295          Message message = ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
296              valueStr, (pos-1), String.valueOf(c));
297          throw new DirectoryException(
298                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
299        }
300    
301    
302        // Skip over any spaces immediately following the opening parenthesis.
303        while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
304        {
305          pos++;
306        }
307    
308        if (pos >= length)
309        {
310          // This means that the end of the value was reached before we could find
311          // the OID.  Ths is illegal.
312          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
313          throw new DirectoryException(
314                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
315        }
316    
317    
318        // The next set of characters must be the rule ID, which is an integer.
319        int ruleIDStartPos = pos;
320        while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
321        {
322          if (! isDigit(c))
323          {
324            Message message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get(
325                valueStr, String.valueOf(c), (pos-1));
326            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
327                                         message);
328          }
329        }
330    
331        // If we're at the end of the value, then it isn't a valid DIT structure
332        // rule description.  Otherwise, parse out the rule ID.
333        int ruleID;
334        if (pos >= length)
335        {
336          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
337          throw new DirectoryException(
338                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
339        }
340        else
341        {
342          ruleID = Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
343        }
344    
345    
346        // Skip over the space(s) after the rule ID.
347        while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
348        {
349          pos++;
350        }
351    
352        if (pos >= length)
353        {
354          // This means that the end of the value was reached before we could find
355          // the rule ID.  Ths is illegal.
356          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
357          throw new DirectoryException(
358                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
359        }
360    
361    
362        // At this point, we should have a pretty specific syntax that describes
363        // what may come next, but some of the components are optional and it would
364        // be pretty easy to put something in the wrong order, so we will be very
365        // flexible about what we can accept.  Just look at the next token, figure
366        // out what it is and how to treat what comes after it, then repeat until
367        // we get to the end of the value.  But before we start, set default values
368        // for everything else we might need to know.
369        LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
370        String description = null;
371        boolean isObsolete = false;
372        NameForm nameForm = null;
373        boolean nameFormGiven = false;
374        LinkedHashSet<DITStructureRule> superiorRules = null;
375        LinkedHashMap<String,List<String>> extraProperties =
376             new LinkedHashMap<String,List<String>>();
377    
378    
379        while (true)
380        {
381          StringBuilder tokenNameBuffer = new StringBuilder();
382          pos = readTokenName(valueStr, tokenNameBuffer, pos);
383          String tokenName = tokenNameBuffer.toString();
384          String lowerTokenName = toLowerCase(tokenName);
385          if (tokenName.equals(")"))
386          {
387            // We must be at the end of the value.  If not, then that's a problem.
388            if (pos < length)
389            {
390              Message message = ERR_ATTR_SYNTAX_DSR_UNEXPECTED_CLOSE_PARENTHESIS.
391                  get(valueStr, (pos-1));
392              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
393                                           message);
394            }
395    
396            break;
397          }
398          else if (lowerTokenName.equals("name"))
399          {
400            // This specifies the set of names for the DIT structure rule.  It may
401            // be a single name in single quotes, or it may be an open parenthesis
402            // followed by one or more names in single quotes separated by spaces.
403            c = valueStr.charAt(pos++);
404            if (c == '\'')
405            {
406              StringBuilder userBuffer  = new StringBuilder();
407              StringBuilder lowerBuffer = new StringBuilder();
408              pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
409                                     (pos-1));
410              names.put(lowerBuffer.toString(), userBuffer.toString());
411            }
412            else if (c == '(')
413            {
414              StringBuilder userBuffer  = new StringBuilder();
415              StringBuilder lowerBuffer = new StringBuilder();
416              pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
417                                     pos);
418              names.put(lowerBuffer.toString(), userBuffer.toString());
419    
420    
421              while (true)
422              {
423                if (valueStr.charAt(pos) == ')')
424                {
425                  // Skip over any spaces after the parenthesis.
426                  pos++;
427                  while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
428                  {
429                    pos++;
430                  }
431    
432                  break;
433                }
434                else
435                {
436                  userBuffer  = new StringBuilder();
437                  lowerBuffer = new StringBuilder();
438    
439                  pos = readQuotedString(valueStr, lowerStr, userBuffer,
440                                         lowerBuffer, pos);
441                  names.put(lowerBuffer.toString(), userBuffer.toString());
442                }
443              }
444            }
445            else
446            {
447              // This is an illegal character.
448              Message message =
449                  ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(
450                          valueStr, String.valueOf(c), (pos-1));
451              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
452                                           message);
453            }
454          }
455          else if (lowerTokenName.equals("desc"))
456          {
457            // This specifies the description for the DIT structure rule.  It is an
458            // arbitrary string of characters enclosed in single quotes.
459            StringBuilder descriptionBuffer = new StringBuilder();
460            pos = readQuotedString(valueStr, descriptionBuffer, pos);
461            description = descriptionBuffer.toString();
462          }
463          else if (lowerTokenName.equals("obsolete"))
464          {
465            // This indicates whether the DIT structure rule should be considered
466            // obsolete.  We do not need to do any more parsing for this token.
467            isObsolete = true;
468          }
469          else if (lowerTokenName.equals("form"))
470          {
471            // This should be the OID of the associated name form.
472            StringBuilder woidBuffer = new StringBuilder();
473            pos = readWOID(lowerStr, woidBuffer, pos);
474    
475            nameFormGiven = true;
476            nameForm = schema.getNameForm(woidBuffer.toString());
477            if ((nameForm == null) && (! allowUnknownElements))
478            {
479              Message message = ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(
480                  valueStr, woidBuffer.toString());
481              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
482                                           message);
483            }
484          }
485          else if (lowerTokenName.equals("sup"))
486          {
487            LinkedList<DITStructureRule> superiorList =
488                 new LinkedList<DITStructureRule>();
489    
490            // This specifies the set of superior rule IDs (which are integers) for
491            // this DIT structure rule.  It may be a single rule ID or a set of
492            // rule IDs enclosed in parentheses and separated by spaces.
493            c = valueStr.charAt(pos++);
494            if (c == '(')
495            {
496              while (true)
497              {
498                // Skip over any leading spaces.
499                while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
500                {
501                  pos++;
502                }
503    
504                if (pos >= length)
505                {
506                  Message message =
507                      ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
508                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
509                                               message);
510                }
511    
512                // Read the next integer value.
513                ruleIDStartPos = pos;
514                while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
515                {
516                  if (! isDigit(c))
517                  {
518                    Message message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.
519                        get(valueStr, String.valueOf(c), (pos-1));
520                    throw new DirectoryException(
521                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
522                  }
523                }
524    
525                // If we're at the end of the value, then it isn't a valid DIT
526                // structure rule description.  Otherwise, parse out the rule ID.
527                int supRuleID;
528                if (pos >= length)
529                {
530                  Message message =
531                      ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
532                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
533                                               message);
534                }
535                else
536                {
537                  supRuleID =
538                       Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
539                }
540    
541    
542                // Get the DIT structure rule with the specified rule ID.
543                DITStructureRule superiorRule =
544                     schema.getDITStructureRule(supRuleID);
545                if (superiorRule == null)
546                {
547                  if (! allowUnknownElements)
548                  {
549                    Message message = ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(
550                        valueStr, supRuleID);
551                    throw new DirectoryException(
552                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
553                  }
554                }
555                else
556                {
557                  superiorList.add(superiorRule);
558                }
559    
560    
561                // Skip over any trailing spaces.
562                while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
563                {
564                  pos++;
565                }
566    
567                if (pos >= length)
568                {
569                  Message message =
570                      ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
571                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
572                                               message);
573                }
574    
575    
576                // If the next character is a closing parenthesis, then read any
577                // spaces after it and break out of the loop.
578                if (c == ')')
579                {
580                  pos++;
581                  while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
582                  {
583                    pos++;
584                  }
585    
586                  if (pos >= length)
587                  {
588                    Message message =
589                        ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
590                    throw new DirectoryException(
591                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
592                  }
593    
594                  break;
595                }
596              }
597            }
598            else
599            {
600              if (pos >= length)
601              {
602                Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
603                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
604                                             message);
605              }
606    
607              // Read the next integer value.
608              ruleIDStartPos = pos - 1;
609              while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
610              {
611                if (! isDigit(c))
612                {
613                  Message message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get(
614                      valueStr, String.valueOf(c), (pos-1));
615                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
616                                               message);
617                }
618              }
619    
620              // If we're at the end of the value, then it isn't a valid DIT
621              // structure rule description.  Otherwise, parse out the rule ID.
622              int supRuleID;
623              if (pos >= length)
624              {
625                Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
626                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
627                                             message);
628              }
629              else
630              {
631                supRuleID =
632                     Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
633              }
634    
635    
636              // Get the DIT structure rule with the specified rule ID.
637              DITStructureRule superiorRule = schema.getDITStructureRule(supRuleID);
638              if (superiorRule == null)
639              {
640                if (! allowUnknownElements)
641                {
642                  Message message =
643                      ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(valueStr, supRuleID);
644                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
645                                               message);
646                }
647              }
648              else
649              {
650                superiorList.add(superiorRule);
651              }
652    
653    
654              // Skip over any trailing spaces.
655              while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
656              {
657                pos++;
658              }
659    
660              if (pos >= length)
661              {
662                Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
663                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
664                                             message);
665              }
666            }
667    
668            superiorRules = new LinkedHashSet<DITStructureRule>(superiorList);
669          }
670          else
671          {
672            // This must be a non-standard property and it must be followed by
673            // either a single value in single quotes or an open parenthesis
674            // followed by one or more values in single quotes separated by spaces
675            // followed by a close parenthesis.
676            LinkedList<String> valueList =
677                 new LinkedList<String>();
678            pos = readExtraParameterValues(valueStr, valueList, pos);
679            extraProperties.put(tokenName, valueList);
680          }
681        }
682    
683    
684        if ((nameForm == null) && (! nameFormGiven))
685        {
686          Message message = ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(valueStr);
687          throw new DirectoryException(
688                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
689        }
690    
691    
692        return new DITStructureRule(value.stringValue(), names, ruleID, description,
693                                    isObsolete, nameForm, superiorRules,
694                                    extraProperties);
695      }
696    
697    
698    
699      /**
700       * Reads the next token name from the DIT content rule definition, skipping
701       * over any leading or trailing spaces, and appends it to the provided buffer.
702       *
703       * @param  valueStr   The string representation of the DIT content rule
704       *                    definition.
705       * @param  tokenName  The buffer into which the token name will be written.
706       * @param  startPos   The position in the provided string at which to start
707       *                    reading the token name.
708       *
709       * @return  The position of the first character that is not part of the token
710       *          name or one of the trailing spaces after it.
711       *
712       * @throws  DirectoryException  If a problem is encountered while reading the
713       *                              token name.
714       */
715      private static int readTokenName(String valueStr, StringBuilder tokenName,
716                                       int startPos)
717              throws DirectoryException
718      {
719        // Skip over any spaces at the beginning of the value.
720        char c = '\u0000';
721        int  length = valueStr.length();
722        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
723        {
724          startPos++;
725        }
726    
727        if (startPos >= length)
728        {
729          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
730          throw new DirectoryException(
731                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
732        }
733    
734    
735        // Read until we find the next space.
736        while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
737        {
738          tokenName.append(c);
739        }
740    
741    
742        // Skip over any trailing spaces after the value.
743        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
744        {
745          startPos++;
746        }
747    
748    
749        // Return the position of the first non-space character after the token.
750        return startPos;
751      }
752    
753    
754    
755      /**
756       * Reads the value of a string enclosed in single quotes, skipping over the
757       * quotes and any leading or trailing spaces, and appending the string to the
758       * provided buffer.
759       *
760       * @param  valueStr     The user-provided representation of the DIT content
761       *                      rule definition.
762       * @param  valueBuffer  The buffer into which the user-provided representation
763       *                      of the value will be placed.
764       * @param  startPos     The position in the provided string at which to start
765       *                      reading the quoted string.
766       *
767       * @return  The position of the first character that is not part of the quoted
768       *          string or one of the trailing spaces after it.
769       *
770       * @throws  DirectoryException  If a problem is encountered while reading the
771       *                              quoted string.
772       */
773      private static int readQuotedString(String valueStr,
774                                          StringBuilder valueBuffer, int startPos)
775              throws DirectoryException
776      {
777        // Skip over any spaces at the beginning of the value.
778        char c = '\u0000';
779        int  length = valueStr.length();
780        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
781        {
782          startPos++;
783        }
784    
785        if (startPos >= length)
786        {
787          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
788          throw new DirectoryException(
789                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
790        }
791    
792    
793        // The next character must be a single quote.
794        if (c != '\'')
795        {
796          Message message =
797              ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(
798                      valueStr, startPos, String.valueOf(c));
799          throw new DirectoryException(
800                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
801        }
802    
803    
804        // Read until we find the closing quote.
805        startPos++;
806        while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\''))
807        {
808          valueBuffer.append(c);
809          startPos++;
810        }
811    
812    
813        // Skip over any trailing spaces after the value.
814        startPos++;
815        while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
816        {
817          startPos++;
818        }
819    
820    
821        // If we're at the end of the value, then that's illegal.
822        if (startPos >= length)
823        {
824          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
825          throw new DirectoryException(
826                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
827        }
828    
829    
830        // Return the position of the first non-space character after the token.
831        return startPos;
832      }
833    
834    
835    
836      /**
837       * Reads the value of a string enclosed in single quotes, skipping over the
838       * quotes and any leading or trailing spaces, and appending the string to the
839       * provided buffer.
840       *
841       * @param  valueStr     The user-provided representation of the DIT content
842       *                      rule definition.
843       * @param  lowerStr     The all-lowercase representation of the DIT content
844       *                      rule definition.
845       * @param  userBuffer   The buffer into which the user-provided representation
846       *                      of the value will be placed.
847       * @param  lowerBuffer  The buffer into which the all-lowercase representation
848       *                      of the value will be placed.
849       * @param  startPos     The position in the provided string at which to start
850       *                      reading the quoted string.
851       *
852       * @return  The position of the first character that is not part of the quoted
853       *          string or one of the trailing spaces after it.
854       *
855       * @throws  DirectoryException  If a problem is encountered while reading the
856       *                              quoted string.
857       */
858      private static int readQuotedString(String valueStr, String lowerStr,
859                                          StringBuilder userBuffer,
860                                          StringBuilder lowerBuffer, int startPos)
861              throws DirectoryException
862      {
863        // Skip over any spaces at the beginning of the value.
864        char c = '\u0000';
865        int  length = lowerStr.length();
866        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
867        {
868          startPos++;
869        }
870    
871        if (startPos >= length)
872        {
873          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
874          throw new DirectoryException(
875                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
876        }
877    
878    
879        // The next character must be a single quote.
880        if (c != '\'')
881        {
882          Message message =
883              ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(
884                      valueStr, startPos, String.valueOf(c));
885          throw new DirectoryException(
886                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
887        }
888    
889    
890        // Read until we find the closing quote.
891        startPos++;
892        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) != '\''))
893        {
894          lowerBuffer.append(c);
895          userBuffer.append(valueStr.charAt(startPos));
896          startPos++;
897        }
898    
899    
900        // Skip over any trailing spaces after the value.
901        startPos++;
902        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
903        {
904          startPos++;
905        }
906    
907    
908        // If we're at the end of the value, then that's illegal.
909        if (startPos >= length)
910        {
911          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
912          throw new DirectoryException(
913                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
914        }
915    
916    
917        // Return the position of the first non-space character after the token.
918        return startPos;
919      }
920    
921    
922    
923      /**
924       * Reads an attributeType/objectclass description or numeric OID from the
925       * provided string, skipping over any leading or trailing spaces, and
926       * appending the value to the provided buffer.
927       *
928       * @param  lowerStr    The string from which the name or OID is to be read.
929       * @param  woidBuffer  The buffer into which the name or OID should be
930       *                     appended.
931       * @param  startPos    The position at which to start reading.
932       *
933       * @return  The position of the first character after the name or OID that is
934       *          not a space.
935       *
936       * @throws  DirectoryException  If a problem is encountered while reading the
937       *                              name or OID.
938       */
939      private static int readWOID(String lowerStr, StringBuilder woidBuffer,
940                                  int startPos)
941              throws DirectoryException
942      {
943        // Skip over any spaces at the beginning of the value.
944        char c = '\u0000';
945        int  length = lowerStr.length();
946        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
947        {
948          startPos++;
949        }
950    
951        if (startPos >= length)
952        {
953          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
954          throw new DirectoryException(
955                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
956        }
957    
958    
959        // The next character must be either numeric (for an OID) or alphabetic (for
960        // an attribute type/objectclass description).
961        if (isDigit(c))
962        {
963          // This must be a numeric OID.  In that case, we will accept only digits
964          // and periods, but not consecutive periods.
965          boolean lastWasPeriod = false;
966          while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
967          {
968            if (c == '.')
969            {
970              if (lastWasPeriod)
971              {
972                Message message = ERR_ATTR_SYNTAX_DSR_DOUBLE_PERIOD_IN_NUMERIC_OID.
973                    get(lowerStr, (startPos-1));
974                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
975                                             message);
976              }
977              else
978              {
979                woidBuffer.append(c);
980                lastWasPeriod = true;
981              }
982            }
983            else if (! isDigit(c))
984            {
985              // Technically, this must be an illegal character.  However, it is
986              // possible that someone just got sloppy and did not include a space
987              // between the name/OID and a closing parenthesis.  In that case,
988              // we'll assume it's the end of the value.  What's more, we'll have
989              // to prematurely return to nasty side effects from stripping off
990              // additional characters.
991              if (c == ')')
992              {
993                return (startPos-1);
994              }
995    
996              // This must have been an illegal character.
997              Message message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_NUMERIC_OID.get(
998                  lowerStr, String.valueOf(c), (startPos-1));
999              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1000                                           message);
1001            }
1002            else
1003            {
1004              woidBuffer.append(c);
1005              lastWasPeriod = false;
1006            }
1007          }
1008        }
1009        else if (isAlpha(c))
1010        {
1011          // This must be an attribute type/objectclass description.  In this case,
1012          // we will only accept alphabetic characters, numeric digits, and the
1013          // hyphen.
1014          while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
1015          {
1016            if (isAlpha(c) || isDigit(c) || (c == '-') ||
1017                ((c == '_') && DirectoryServer.allowAttributeNameExceptions()))
1018            {
1019              woidBuffer.append(c);
1020            }
1021            else
1022            {
1023              // Technically, this must be an illegal character.  However, it is
1024              // possible that someone just got sloppy and did not include a space
1025              // between the name/OID and a closing parenthesis.  In that case,
1026              // we'll assume it's the end of the value.  What's more, we'll have
1027              // to prematurely return to nasty side effects from stripping off
1028              // additional characters.
1029              if (c == ')')
1030              {
1031                return (startPos-1);
1032              }
1033    
1034              // This must have been an illegal character.
1035              Message message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_STRING_OID.get(
1036                  lowerStr, String.valueOf(c), (startPos-1));
1037              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1038                                           message);
1039            }
1040          }
1041        }
1042        else
1043        {
1044          Message message =
1045              ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(
1046                      lowerStr, String.valueOf(c), startPos);
1047          throw new DirectoryException(
1048                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1049        }
1050    
1051    
1052        // Skip over any trailing spaces after the value.
1053        while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1054        {
1055          startPos++;
1056        }
1057    
1058    
1059        // If we're at the end of the value, then that's illegal.
1060        if (startPos >= length)
1061        {
1062          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
1063          throw new DirectoryException(
1064                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1065        }
1066    
1067    
1068        // Return the position of the first non-space character after the token.
1069        return startPos;
1070      }
1071    
1072    
1073    
1074      /**
1075       * Reads the value for an "extra" parameter.  It will handle a single unquoted
1076       * word (which is technically illegal, but we'll allow it), a single quoted
1077       * string, or an open parenthesis followed by a space-delimited set of quoted
1078       * strings or unquoted words followed by a close parenthesis.
1079       *
1080       * @param  valueStr   The string containing the information to be read.
1081       * @param  valueList  The list of "extra" parameter values read so far.
1082       * @param  startPos   The position in the value string at which to start
1083       *                    reading.
1084       *
1085       * @return  The "extra" parameter value that was read.
1086       *
1087       * @throws  DirectoryException  If a problem occurs while attempting to read
1088       *                              the value.
1089       */
1090      private static int readExtraParameterValues(String valueStr,
1091                              List<String> valueList, int startPos)
1092              throws DirectoryException
1093      {
1094        // Skip over any leading spaces.
1095        int length = valueStr.length();
1096        char c = valueStr.charAt(startPos++);
1097        while ((startPos < length) && (c == ' '))
1098        {
1099          c = valueStr.charAt(startPos++);
1100        }
1101    
1102        if (startPos >= length)
1103        {
1104          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
1105          throw new DirectoryException(
1106                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1107        }
1108    
1109    
1110        // Look at the next character.  If it is a quote, then parse until the next
1111        // quote and end.  If it is an open parenthesis, then parse individual
1112        // values until the close parenthesis and end.  Otherwise, parse until the
1113        // next space and end.
1114        if (c == '\'')
1115        {
1116          // Parse until the closing quote.
1117          StringBuilder valueBuffer = new StringBuilder();
1118          while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != '\''))
1119          {
1120            valueBuffer.append(c);
1121          }
1122    
1123          valueList.add(valueBuffer.toString());
1124        }
1125        else if (c == '(')
1126        {
1127          while (true)
1128          {
1129            // Skip over any leading spaces;
1130            startPos++;
1131            while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
1132            {
1133              startPos++;
1134            }
1135    
1136            if (startPos >= length)
1137            {
1138              Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
1139              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1140                                           message);
1141            }
1142    
1143    
1144            if (c == ')')
1145            {
1146              // This is the end of the list.
1147              break;
1148            }
1149            else if (c == '(')
1150            {
1151              // This is an illegal character.
1152              Message message =
1153                  ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(
1154                          valueStr, String.valueOf(c), startPos);
1155              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1156                                           message);
1157            }
1158            else
1159            {
1160              // We'll recursively call this method to deal with this.
1161              startPos = readExtraParameterValues(valueStr, valueList, startPos);
1162            }
1163          }
1164        }
1165        else
1166        {
1167          // Parse until the next space.
1168          StringBuilder valueBuffer = new StringBuilder();
1169          while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
1170          {
1171            valueBuffer.append(c);
1172          }
1173    
1174          valueList.add(valueBuffer.toString());
1175        }
1176    
1177    
1178    
1179        // Skip over any trailing spaces.
1180        while ((startPos < length) && (valueStr.charAt(startPos) == ' '))
1181        {
1182          startPos++;
1183        }
1184    
1185        if (startPos >= length)
1186        {
1187          Message message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
1188          throw new DirectoryException(
1189                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1190        }
1191    
1192    
1193        return startPos;
1194      }
1195    }
1196