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    
029    
030    
031    import org.opends.server.admin.std.server.AttributeSyntaxCfg;
032    import org.opends.server.api.ApproximateMatchingRule;
033    import org.opends.server.api.AttributeSyntax;
034    import org.opends.server.api.EqualityMatchingRule;
035    import org.opends.server.api.OrderingMatchingRule;
036    import org.opends.server.api.SubstringMatchingRule;
037    import org.opends.server.config.ConfigException;
038    import org.opends.server.core.DirectoryServer;
039    import org.opends.server.types.ByteString;
040    
041    
042    
043    import static org.opends.server.loggers.ErrorLogger.*;
044    import static org.opends.messages.SchemaMessages.*;
045    import org.opends.messages.MessageBuilder;
046    import static org.opends.server.schema.SchemaConstants.*;
047    import static org.opends.server.util.StaticUtils.*;
048    
049    
050    /**
051     * This class implements the guide attribute syntax, which may be used to
052     * provide criteria for generating search filters for entries, optionally tied
053     * to a specified objectclass.
054     */
055    public class GuideSyntax
056           extends AttributeSyntax<AttributeSyntaxCfg>
057    {
058      // The default equality matching rule for this syntax.
059      private EqualityMatchingRule defaultEqualityMatchingRule;
060    
061      // The default ordering matching rule for this syntax.
062      private OrderingMatchingRule defaultOrderingMatchingRule;
063    
064      // The default substring matching rule for this syntax.
065      private SubstringMatchingRule defaultSubstringMatchingRule;
066    
067    
068    
069      /**
070       * Creates a new instance of this syntax.  Note that the only thing that
071       * should be done here is to invoke the default constructor for the
072       * superclass.  All initialization should be performed in the
073       * <CODE>initializeSyntax</CODE> method.
074       */
075      public GuideSyntax()
076      {
077        super();
078      }
079    
080    
081    
082      /**
083       * {@inheritDoc}
084       */
085      public void initializeSyntax(AttributeSyntaxCfg configuration)
086             throws ConfigException
087      {
088        defaultEqualityMatchingRule =
089             DirectoryServer.getEqualityMatchingRule(EMR_OCTET_STRING_OID);
090        if (defaultEqualityMatchingRule == null)
091        {
092          logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
093              EMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME));
094        }
095    
096        defaultOrderingMatchingRule =
097             DirectoryServer.getOrderingMatchingRule(OMR_OCTET_STRING_OID);
098        if (defaultOrderingMatchingRule == null)
099        {
100          logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
101              OMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME));
102        }
103    
104        defaultSubstringMatchingRule =
105             DirectoryServer.getSubstringMatchingRule(SMR_OCTET_STRING_OID);
106        if (defaultSubstringMatchingRule == null)
107        {
108          logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
109              SMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME));
110        }
111      }
112    
113    
114    
115      /**
116       * Retrieves the common name for this attribute syntax.
117       *
118       * @return  The common name for this attribute syntax.
119       */
120      public String getSyntaxName()
121      {
122        return SYNTAX_GUIDE_NAME;
123      }
124    
125    
126    
127      /**
128       * Retrieves the OID for this attribute syntax.
129       *
130       * @return  The OID for this attribute syntax.
131       */
132      public String getOID()
133      {
134        return SYNTAX_GUIDE_OID;
135      }
136    
137    
138    
139      /**
140       * Retrieves a description for this attribute syntax.
141       *
142       * @return  A description for this attribute syntax.
143       */
144      public String getDescription()
145      {
146        return SYNTAX_GUIDE_DESCRIPTION;
147      }
148    
149    
150    
151      /**
152       * Retrieves the default equality matching rule that will be used for
153       * attributes with this syntax.
154       *
155       * @return  The default equality matching rule that will be used for
156       *          attributes with this syntax, or <CODE>null</CODE> if equality
157       *          matches will not be allowed for this type by default.
158       */
159      public EqualityMatchingRule getEqualityMatchingRule()
160      {
161        return defaultEqualityMatchingRule;
162      }
163    
164    
165    
166      /**
167       * Retrieves the default ordering matching rule that will be used for
168       * attributes with this syntax.
169       *
170       * @return  The default ordering matching rule that will be used for
171       *          attributes with this syntax, or <CODE>null</CODE> if ordering
172       *          matches will not be allowed for this type by default.
173       */
174      public OrderingMatchingRule getOrderingMatchingRule()
175      {
176        return defaultOrderingMatchingRule;
177      }
178    
179    
180    
181      /**
182       * Retrieves the default substring matching rule that will be used for
183       * attributes with this syntax.
184       *
185       * @return  The default substring matching rule that will be used for
186       *          attributes with this syntax, or <CODE>null</CODE> if substring
187       *          matches will not be allowed for this type by default.
188       */
189      public SubstringMatchingRule getSubstringMatchingRule()
190      {
191        return defaultSubstringMatchingRule;
192      }
193    
194    
195    
196      /**
197       * Retrieves the default approximate matching rule that will be used for
198       * attributes with this syntax.
199       *
200       * @return  The default approximate matching rule that will be used for
201       *          attributes with this syntax, or <CODE>null</CODE> if approximate
202       *          matches will not be allowed for this type by default.
203       */
204      public ApproximateMatchingRule getApproximateMatchingRule()
205      {
206        // There is no approximate matching rule by default.
207        return null;
208      }
209    
210    
211    
212      /**
213       * Indicates whether the provided value is acceptable for use in an attribute
214       * with this syntax.  If it is not, then the reason may be appended to the
215       * provided buffer.
216       *
217       * @param  value          The value for which to make the determination.
218       * @param  invalidReason  The buffer to which the invalid reason should be
219       *                        appended.
220       *
221       * @return  <CODE>true</CODE> if the provided value is acceptable for use with
222       *          this syntax, or <CODE>false</CODE> if not.
223       */
224      public boolean valueIsAcceptable(ByteString value,
225                                       MessageBuilder invalidReason)
226      {
227        // Get a lowercase string version of the provided value.
228        String valueStr = toLowerCase(value.stringValue());
229    
230    
231        // Find the position of the octothorpe.  If there isn't one, then the entire
232        // value should be the criteria.
233        int sharpPos = valueStr.indexOf('#');
234        if (sharpPos < 0)
235        {
236          return criteriaIsValid(valueStr, valueStr, invalidReason);
237        }
238    
239    
240        // Get the objectclass and see if it is a valid name or OID.
241        String ocName   = valueStr.substring(0, sharpPos).trim();
242        int    ocLength = ocName.length();
243        if (ocLength == 0)
244        {
245    
246          invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_OC.get(valueStr));
247          return false;
248        }
249    
250        if (! isValidSchemaElement(ocName, 0, ocLength, invalidReason))
251        {
252          return false;
253        }
254    
255    
256        // The rest of the value must be the criteria.
257        return criteriaIsValid(valueStr.substring(sharpPos+1), valueStr,
258                invalidReason);
259      }
260    
261    
262    
263      /**
264       * Determines whether the provided string represents a valid criteria
265       * according to the guide syntax.
266       *
267       * @param  criteria       The portion of the criteria for which to make the
268       *                        determination.
269       * @param  valueStr       The complete guide value provided by the client.
270       * @param  invalidReason  The buffer to which to append the reason that the
271       *                        criteria is invalid if a problem is found.
272       *
273       * @return  <CODE>true</CODE> if the provided string does contain a valid
274       *          criteria, or <CODE>false</CODE> if not.
275       */
276      public static boolean criteriaIsValid(String criteria, String valueStr,
277                                            MessageBuilder invalidReason)
278      {
279        // See if the criteria starts with a '!'.  If so, then just evaluate
280        // everything after that as a criteria.
281        char c = criteria.charAt(0);
282        if (c == '!')
283        {
284          return criteriaIsValid(criteria.substring(1), valueStr, invalidReason);
285        }
286    
287    
288        // See if the criteria starts with a '('.  If so, then find the
289        // corresponding ')' and parse what's in between as a criteria.
290        if (c == '(')
291        {
292          int length = criteria.length();
293          int depth  = 1;
294    
295          for (int i=1; i < length; i++)
296          {
297            c = criteria.charAt(i);
298            if (c == ')')
299            {
300              depth--;
301              if (depth == 0)
302              {
303                String subCriteria = criteria.substring(1, i);
304                if (! criteriaIsValid(subCriteria, valueStr, invalidReason))
305                {
306                  return false;
307                }
308    
309                // If we are at the end of the value, then it was valid.  Otherwise,
310                // the next character must be a pipe or an ampersand followed by
311                // another set of criteria.
312                if (i == (length-1))
313                {
314                  return true;
315                }
316                else
317                {
318                  c = criteria.charAt(i+1);
319                  if ((c == '|') || (c == '&'))
320                  {
321                    return criteriaIsValid(criteria.substring(i+2), valueStr,
322                                           invalidReason);
323                  }
324                  else
325                  {
326    
327                    invalidReason.append(
328                            ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
329                                    valueStr, criteria, c, (i+1)));
330                    return false;
331                  }
332                }
333              }
334            }
335            else if (c == '(')
336            {
337              depth++;
338            }
339          }
340    
341    
342          // If we've gotten here, then we went through the entire value without
343          // finding the appropriate closing parenthesis.
344    
345          invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_MISSING_CLOSE_PAREN.get(
346                  valueStr, criteria));
347          return false;
348        }
349    
350    
351        // See if the criteria starts with a '?'.  If so, then it must be either
352        // "?true" or "?false".
353        if (c == '?')
354        {
355          if (criteria.startsWith("?true"))
356          {
357            if (criteria.length() == 5)
358            {
359              return true;
360            }
361            else
362            {
363              // The only characters allowed next are a pipe or an ampersand.
364              c = criteria.charAt(5);
365              if ((c == '|') || (c == '&'))
366              {
367                return criteriaIsValid(criteria.substring(6), valueStr,
368                                       invalidReason);
369              }
370              else
371              {
372                invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
373                        valueStr, criteria, c, 5));
374                return false;
375              }
376            }
377          }
378          else if (criteria.startsWith("?false"))
379          {
380            if (criteria.length() == 6)
381            {
382              return true;
383            }
384            else
385            {
386              // The only characters allowed next are a pipe or an ampersand.
387              c = criteria.charAt(6);
388              if ((c == '|') || (c == '&'))
389              {
390                return criteriaIsValid(criteria.substring(7), valueStr,
391                                       invalidReason);
392              }
393              else
394              {
395                invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
396                        valueStr, criteria, c, 6));
397                return false;
398              }
399            }
400          }
401          else
402          {
403            invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_QUESTION_MARK.get(
404                    valueStr, criteria));
405            return false;
406          }
407        }
408    
409    
410        // See if the criteria is either "true" or "false".  If so, then it is
411        // valid.
412        if (criteria.equals("true") || criteria.equals("false"))
413        {
414          return true;
415        }
416    
417    
418        // The only thing that will be allowed is an attribute type name or OID
419        // followed by a dollar sign and a match type.  Find the dollar sign and
420        // verify whether the value before it is a valid attribute type name or OID.
421        int dollarPos = criteria.indexOf('$');
422        if (dollarPos < 0)
423        {
424          invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_DOLLAR.get(
425                  valueStr, criteria));
426          return false;
427        }
428        else if (dollarPos == 0)
429        {
430          invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_ATTR.get(
431                  valueStr, criteria));
432          return false;
433        }
434        else if (dollarPos == (criteria.length()-1))
435        {
436          invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_MATCH_TYPE.get(
437                  valueStr, criteria));
438          return false;
439        }
440        else
441        {
442          if (! isValidSchemaElement(criteria, 0, dollarPos, invalidReason))
443          {
444            return false;
445          }
446        }
447    
448    
449        // The substring immediately after the dollar sign must be one of "eq",
450        // "substr", "ge", "le", or "approx".  It may be followed by the end of the
451        // value, a pipe, or an ampersand.
452        int endPos;
453        c = criteria.charAt(dollarPos+1);
454        switch (c)
455        {
456          case 'e':
457            if (criteria.startsWith("eq", dollarPos+1))
458            {
459              endPos = dollarPos + 3;
460              break;
461            }
462            else
463            {
464              invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
465                      valueStr, criteria, dollarPos+1));
466              return false;
467            }
468    
469          case 's':
470            if (criteria.startsWith("substr", dollarPos+1))
471            {
472              endPos = dollarPos + 7;
473              break;
474            }
475            else
476            {
477              invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
478                      valueStr, criteria, dollarPos+1));
479              return false;
480            }
481    
482          case 'g':
483            if (criteria.startsWith("ge", dollarPos+1))
484            {
485              endPos = dollarPos + 3;
486              break;
487            }
488            else
489            {
490              invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
491                      valueStr, criteria, dollarPos+1));
492              return false;
493            }
494    
495          case 'l':
496            if (criteria.startsWith("le", dollarPos+1))
497            {
498              endPos = dollarPos + 3;
499              break;
500            }
501            else
502            {
503              invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
504                      valueStr, criteria, dollarPos+1));
505              return false;
506            }
507    
508          case 'a':
509            if (criteria.startsWith("approx", dollarPos+1))
510            {
511              endPos = dollarPos + 7;
512              break;
513            }
514            else
515            {
516              invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
517                      valueStr, criteria, dollarPos+1));
518              return false;
519            }
520    
521          default:
522            invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get(
523                    valueStr, criteria, dollarPos+1));
524            return false;
525        }
526    
527    
528        // See if we are at the end of the value.  If so, then it is valid.
529        // Otherwise, the next character must be a pipe or an ampersand.
530        if (endPos >= criteria.length())
531        {
532          return true;
533        }
534        else
535        {
536          c = criteria.charAt(endPos);
537          if ((c == '|') || (c == '&'))
538          {
539            return criteriaIsValid(criteria.substring(endPos+1), valueStr,
540                                   invalidReason);
541          }
542          else
543          {
544            invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
545                    valueStr, criteria, c, endPos));
546            return false;
547          }
548        }
549      }
550    }
551