001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 * Portions Copyright 2013-2014 Manuel Gaupp
017 */
018package org.opends.server.types;
019
020import static org.opends.messages.CoreMessages.*;
021import static org.opends.server.util.ServerConstants.*;
022import static org.opends.server.util.StaticUtils.*;
023
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.LinkedHashSet;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Set;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.forgerock.opendj.ldap.AVA;
036import org.forgerock.opendj.ldap.Assertion;
037import org.forgerock.opendj.ldap.AttributeDescription;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.forgerock.opendj.ldap.ConditionResult;
041import org.forgerock.opendj.ldap.RDN;
042import org.forgerock.opendj.ldap.ResultCode;
043import org.forgerock.opendj.ldap.schema.AttributeType;
044import org.forgerock.opendj.ldap.schema.MatchingRule;
045import org.opends.server.core.DirectoryServer;
046
047/**
048 * This class defines a data structure for storing and interacting
049 * with a search filter that may serve as criteria for locating
050 * entries in the Directory Server.
051 */
052@org.opends.server.types.PublicAPI(
053     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
054     mayInstantiate=true,
055     mayExtend=false,
056     mayInvoke=true)
057public final class SearchFilter
058{
059  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
060
061  private static SearchFilter objectClassPresent;
062
063  /** The attribute description for this filter. */
064  private final AttributeDescription attributeDescription;
065  /** The attribute type for this filter. */
066  private final AttributeType attributeType;
067
068  /** The assertion value for this filter. */
069  private final ByteString assertionValue;
070
071  /** Indicates whether to match on DN attributes for extensible match filters. */
072  private final boolean dnAttributes;
073
074  /** The subInitial element for substring filters. */
075  private final ByteString subInitialElement;
076  /** The set of subAny components for substring filters. */
077  private final List<ByteString> subAnyElements;
078  /** The subFinal element for substring filters. */
079  private final ByteString subFinalElement;
080
081  /** The search filter type for this filter. */
082  private final FilterType filterType;
083
084  /** The set of filter components for AND and OR filters. */
085  private final LinkedHashSet<SearchFilter> filterComponents;
086  /** The not filter component for this search filter. */
087  private final SearchFilter notComponent;
088
089  /** The matching rule ID for this search filter. */
090  private final String matchingRuleID;
091
092
093
094  /**
095   * Creates a new search filter with the provided information.
096   *
097   * @param  filterType         The filter type for this search
098   *                            filter.
099   * @param  filterComponents   The set of filter components for AND
100   *                            and OR filters.
101   * @param  notComponent       The filter component for NOT filters.
102   * @param  attributeType      The attribute type for this filter.
103   * @param  attributeOptions   The set of attribute options for the
104   *                            associated attribute type.
105   * @param  assertionValue     The assertion value for this filter.
106   * @param  subInitialElement  The subInitial element for substring
107   *                            filters.
108   * @param  subAnyElements     The subAny elements for substring
109   *                            filters.
110   * @param  subFinalElement    The subFinal element for substring
111   *                            filters.
112   * @param  matchingRuleID     The matching rule ID for this search
113   *                            filter.
114   * @param  dnAttributes       Indicates whether to match on DN
115   *                            attributes for extensible match
116   *                            filters.
117   *
118   * FIXME: this should be private.
119   */
120  public SearchFilter(FilterType filterType,
121                      Collection<SearchFilter> filterComponents,
122                      SearchFilter notComponent,
123                      AttributeType attributeType,
124                      Set<String> attributeOptions,
125                      ByteString assertionValue,
126                      ByteString subInitialElement,
127                      List<ByteString> subAnyElements,
128                      ByteString subFinalElement,
129                      String matchingRuleID, boolean dnAttributes)
130  {
131    // This used to happen in getSubAnyElements, but we do it here
132    // so that we can make this.subAnyElements final.
133    if (subAnyElements == null) {
134      subAnyElements = new ArrayList<>(0);
135    }
136
137    // This used to happen in getFilterComponents, but we do it here
138    // so that we can make this.filterComponents final.
139    if (filterComponents == null) {
140      filterComponents = Collections.emptyList();
141    }
142
143    this.filterType        = filterType;
144    this.filterComponents  = new LinkedHashSet<>(filterComponents);
145    this.notComponent      = notComponent;
146    this.attributeDescription = attributeType != null
147        ? AttributeDescription.create(attributeType, attributeOptions)
148        : null;
149    this.attributeType     = attributeType;
150    this.assertionValue    = assertionValue;
151    this.subInitialElement = subInitialElement;
152    this.subAnyElements    = subAnyElements;
153    this.subFinalElement   = subFinalElement;
154    this.matchingRuleID    = matchingRuleID;
155    this.dnAttributes      = dnAttributes;
156  }
157
158
159  /**
160   * Creates a new AND search filter with the provided information.
161   *
162   * @param  filterComponents  The set of filter components for the
163   * AND filter.
164   *
165   * @return  The constructed search filter.
166   */
167  public static SearchFilter createANDFilter(Collection<SearchFilter>
168                                                  filterComponents)
169  {
170    return new SearchFilter(FilterType.AND, filterComponents, null,
171                            null, null, null, null, null, null, null,
172                            false);
173  }
174
175
176
177  /**
178   * Creates a new OR search filter with the provided information.
179   *
180   * @param  filterComponents  The set of filter components for the OR
181   *                           filter.
182   *
183   * @return  The constructed search filter.
184   */
185  public static SearchFilter createORFilter(Collection<SearchFilter>
186                                                 filterComponents)
187  {
188    return new SearchFilter(FilterType.OR, filterComponents, null,
189                            null, null, null, null, null, null, null,
190                            false);
191  }
192
193
194
195  /**
196   * Creates a new NOT search filter with the provided information.
197   *
198   * @param  notComponent  The filter component for this NOT filter.
199   *
200   * @return  The constructed search filter.
201   */
202  public static SearchFilter createNOTFilter(
203                                  SearchFilter notComponent)
204  {
205    return new SearchFilter(FilterType.NOT, null, notComponent, null,
206                            null, null, null, null, null, null,
207                            false);
208  }
209
210
211
212  /**
213   * Creates a new equality search filter with the provided
214   * information.
215   *
216   * @param  attributeType   The attribute type for this equality
217   *                         filter.
218   * @param  assertionValue  The assertion value for this equality
219   *                         filter.
220   *
221   * @return  The constructed search filter.
222   */
223  public static SearchFilter createEqualityFilter(
224                                  AttributeType attributeType,
225                                  ByteString assertionValue)
226  {
227    return new SearchFilter(FilterType.EQUALITY, null, null,
228                            attributeType, null, assertionValue, null,
229                            null, null, null, false);
230  }
231
232
233
234  /**
235   * Creates a new equality search filter with the provided
236   * information.
237   *
238   * @param  attributeType     The attribute type for this equality
239   *                           filter.
240   * @param  attributeOptions  The set of attribute options for this
241   *                           equality filter.
242   * @param  assertionValue    The assertion value for this equality
243   *                           filter.
244   *
245   * @return  The constructed search filter.
246   */
247  public static SearchFilter createEqualityFilter(
248                                  AttributeType attributeType,
249                                  Set<String> attributeOptions,
250                                  ByteString assertionValue)
251  {
252    return new SearchFilter(FilterType.EQUALITY, null, null,
253                            attributeType, attributeOptions,
254                            assertionValue, null, null, null, null,
255                            false);
256  }
257
258
259
260  /**
261   * Creates a new substring search filter with the provided
262   * information.
263   *
264   * @param  attributeType      The attribute type for this filter.
265   * @param  subInitialElement  The subInitial element for substring
266   *                            filters.
267   * @param  subAnyElements     The subAny elements for substring
268   *                            filters.
269   * @param  subFinalElement    The subFinal element for substring
270   *                            filters.
271   *
272   * @return  The constructed search filter.
273   */
274  public static SearchFilter
275       createSubstringFilter(AttributeType attributeType,
276                             ByteString subInitialElement,
277                             List<ByteString> subAnyElements,
278                             ByteString subFinalElement)
279  {
280    return new SearchFilter(FilterType.SUBSTRING, null, null,
281                            attributeType, null, null,
282                            subInitialElement, subAnyElements,
283                            subFinalElement, null, false);
284  }
285
286
287
288  /**
289   * Creates a new substring search filter with the provided
290   * information.
291   *
292   * @param  attributeType      The attribute type for this filter.
293   * @param  attributeOptions   The set of attribute options for this
294   *                            search filter.
295   * @param  subInitialElement  The subInitial element for substring
296   *                            filters.
297   * @param  subAnyElements     The subAny elements for substring
298   *                            filters.
299   * @param  subFinalElement    The subFinal element for substring
300   *                            filters.
301   *
302   * @return  The constructed search filter.
303   */
304  public static SearchFilter
305       createSubstringFilter(AttributeType attributeType,
306                             Set<String> attributeOptions,
307                             ByteString subInitialElement,
308                             List<ByteString> subAnyElements,
309                             ByteString subFinalElement)
310  {
311    return new SearchFilter(FilterType.SUBSTRING, null, null,
312                            attributeType, attributeOptions, null,
313                            subInitialElement, subAnyElements,
314                            subFinalElement, null, false);
315  }
316
317
318
319  /**
320   * Creates a greater-or-equal search filter with the provided
321   * information.
322   *
323   * @param  attributeType   The attribute type for this
324   *                         greater-or-equal filter.
325   * @param  assertionValue  The assertion value for this
326   *                         greater-or-equal filter.
327   *
328   * @return  The constructed search filter.
329   */
330  public static SearchFilter createGreaterOrEqualFilter(
331                                  AttributeType attributeType,
332                                  ByteString assertionValue)
333  {
334    return new SearchFilter(FilterType.GREATER_OR_EQUAL, null, null,
335                            attributeType, null, assertionValue, null,
336                            null, null, null, false);
337  }
338
339
340
341  /**
342   * Creates a greater-or-equal search filter with the provided
343   * information.
344   *
345   * @param  attributeType     The attribute type for this
346   *                           greater-or-equal filter.
347   * @param  attributeOptions  The set of attribute options for this
348   *                           search filter.
349   * @param  assertionValue    The assertion value for this
350   *                           greater-or-equal filter.
351   *
352   * @return  The constructed search filter.
353   */
354  public static SearchFilter createGreaterOrEqualFilter(
355                                  AttributeType attributeType,
356                                  Set<String> attributeOptions,
357                                  ByteString assertionValue)
358  {
359    return new SearchFilter(FilterType.GREATER_OR_EQUAL, null, null,
360                            attributeType, attributeOptions,
361                            assertionValue, null, null, null, null,
362                            false);
363  }
364
365
366
367  /**
368   * Creates a less-or-equal search filter with the provided
369   * information.
370   *
371   * @param  attributeType   The attribute type for this less-or-equal
372   *                         filter.
373   * @param  assertionValue  The assertion value for this
374   *                         less-or-equal filter.
375   *
376   * @return  The constructed search filter.
377   */
378  public static SearchFilter createLessOrEqualFilter(
379                                  AttributeType attributeType,
380                                  ByteString assertionValue)
381  {
382    return new SearchFilter(FilterType.LESS_OR_EQUAL, null, null,
383                            attributeType, null, assertionValue, null,
384                            null, null, null, false);
385  }
386
387
388
389  /**
390   * Creates a less-or-equal search filter with the provided
391   * information.
392   *
393   * @param  attributeType     The attribute type for this
394   *                           less-or-equal filter.
395   * @param  attributeOptions  The set of attribute options for this
396   *                           search filter.
397   * @param  assertionValue    The assertion value for this
398   *                           less-or-equal filter.
399   *
400   * @return  The constructed search filter.
401   */
402  public static SearchFilter createLessOrEqualFilter(
403                                  AttributeType attributeType,
404                                  Set<String> attributeOptions,
405                                  ByteString assertionValue)
406  {
407    return new SearchFilter(FilterType.LESS_OR_EQUAL, null, null,
408                            attributeType, attributeOptions,
409                            assertionValue, null, null, null, null,
410                            false);
411  }
412
413
414
415  /**
416   * Creates a presence search filter with the provided information.
417   *
418   * @param  attributeType  The attribute type for this presence
419   *                        filter.
420   *
421   * @return  The constructed search filter.
422   */
423  public static SearchFilter createPresenceFilter(
424                                  AttributeType attributeType)
425  {
426    return new SearchFilter(FilterType.PRESENT, null, null,
427                            attributeType, null, null, null, null,
428                            null, null, false);
429  }
430
431
432
433  /**
434   * Creates a presence search filter with the provided information.
435   *
436   * @param  attributeType     The attribute type for this presence
437   *                           filter.
438   * @param  attributeOptions  The attribute options for this presence
439   *                           filter.
440   *
441   * @return  The constructed search filter.
442   */
443  public static SearchFilter createPresenceFilter(
444                                  AttributeType attributeType,
445                                  Set<String> attributeOptions)
446  {
447    return new SearchFilter(FilterType.PRESENT, null, null,
448                            attributeType, attributeOptions, null,
449                            null, null, null, null, false);
450  }
451
452
453
454  /**
455   * Creates an approximate search filter with the provided
456   * information.
457   *
458   * @param  attributeType   The attribute type for this approximate
459   *                         filter.
460   * @param  assertionValue  The assertion value for this approximate
461   *                         filter.
462   *
463   * @return  The constructed search filter.
464   */
465  public static SearchFilter createApproximateFilter(
466                                  AttributeType attributeType,
467                                  ByteString assertionValue)
468  {
469    return new SearchFilter(FilterType.APPROXIMATE_MATCH, null, null,
470                            attributeType, null, assertionValue, null,
471                            null, null, null, false);
472  }
473
474
475
476  /**
477   * Creates an approximate search filter with the provided
478   * information.
479   *
480   * @param  attributeType     The attribute type for this approximate
481   *                           filter.
482   * @param  attributeOptions  The attribute options for this
483   *                           approximate filter.
484   * @param  assertionValue    The assertion value for this
485   *                           approximate filter.
486   *
487   * @return  The constructed search filter.
488   */
489  public static SearchFilter createApproximateFilter(
490                                  AttributeType attributeType,
491                                  Set<String> attributeOptions,
492                                  ByteString assertionValue)
493  {
494    return new SearchFilter(FilterType.APPROXIMATE_MATCH, null, null,
495                            attributeType, attributeOptions,
496                            assertionValue, null, null, null, null,
497                            false);
498  }
499
500
501
502  /**
503   * Creates an extensible matching filter with the provided
504   * information.
505   *
506   * @param  attributeType   The attribute type for this extensible
507   *                         match filter.
508   * @param  assertionValue  The assertion value for this extensible
509   *                         match filter.
510   * @param  matchingRuleID  The matching rule ID for this search
511   *                         filter.
512   * @param  dnAttributes    Indicates whether to match on DN
513   *                         attributes for extensible match filters.
514   *
515   * @return  The constructed search filter.
516   *
517   * @throws  DirectoryException  If the provided information is not
518   *                              sufficient to create an extensible
519   *                              match filter.
520   */
521  public static SearchFilter createExtensibleMatchFilter(
522                                  AttributeType attributeType,
523                                  ByteString assertionValue,
524                                  String matchingRuleID,
525                                  boolean dnAttributes)
526         throws DirectoryException
527  {
528    if (attributeType == null && matchingRuleID == null)
529    {
530      LocalizableMessage message =
531          ERR_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR.get();
532      throw new DirectoryException(
533              ResultCode.PROTOCOL_ERROR, message);
534    }
535
536    return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
537                            attributeType, null, assertionValue, null,
538                            null, null, matchingRuleID, dnAttributes);
539  }
540
541
542
543  /**
544   * Creates an extensible matching filter with the provided
545   * information.
546   *
547   * @param  attributeType     The attribute type for this extensible
548   *                           match filter.
549   * @param  attributeOptions  The set of attribute options for this
550   *                           extensible match filter.
551   * @param  assertionValue    The assertion value for this extensible
552   *                           match filter.
553   * @param  matchingRuleID    The matching rule ID for this search
554   *                           filter.
555   * @param  dnAttributes      Indicates whether to match on DN
556   *                           attributes for extensible match
557   *                           filters.
558   *
559   * @return  The constructed search filter.
560   *
561   * @throws  DirectoryException  If the provided information is not
562   *                              sufficient to create an extensible
563   *                              match filter.
564   */
565  public static SearchFilter createExtensibleMatchFilter(
566                                  AttributeType attributeType,
567                                  Set<String> attributeOptions,
568                                  ByteString assertionValue,
569                                  String matchingRuleID,
570                                  boolean dnAttributes)
571         throws DirectoryException
572  {
573    if (attributeType == null && matchingRuleID == null)
574    {
575      LocalizableMessage message =
576          ERR_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR.get();
577      throw new DirectoryException(
578              ResultCode.PROTOCOL_ERROR, message);
579    }
580
581    return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
582                            attributeType, attributeOptions,
583                            assertionValue, null, null, null,
584                            matchingRuleID, dnAttributes);
585  }
586
587
588
589  /**
590   * Decodes the provided filter string as a search filter.
591   *
592   * @param  filterString  The filter string to be decoded as a search
593   *                       filter.
594   *
595   * @return  The search filter decoded from the provided string.
596   *
597   * @throws  DirectoryException  If a problem occurs while attempting
598   *                              to decode the provided string as a
599   *                              search filter.
600   */
601  public static SearchFilter createFilterFromString(
602                                  String filterString)
603         throws DirectoryException
604  {
605    if (filterString == null)
606    {
607      LocalizableMessage message = ERR_SEARCH_FILTER_NULL.get();
608      throw new DirectoryException(
609              ResultCode.PROTOCOL_ERROR, message);
610    }
611
612
613    try
614    {
615      return createFilterFromString(filterString, 0,
616                                    filterString.length());
617    }
618    catch (DirectoryException de)
619    {
620      logger.traceException(de);
621
622      throw de;
623    }
624    catch (Exception e)
625    {
626      logger.traceException(e);
627
628      LocalizableMessage message = ERR_SEARCH_FILTER_UNCAUGHT_EXCEPTION.get(filterString, e);
629      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e);
630    }
631  }
632
633
634
635  /**
636   * Creates a new search filter from the specified portion of the
637   * provided string.
638   *
639   * @param  filterString  The string containing the filter
640   *                       information to be decoded.
641   * @param  startPos      The index of the first character in the
642   *                       string that is part of the search filter.
643   * @param  endPos        The index of the first character after the
644   *                       start position that is not part of the
645   *                       search filter.
646   *
647   * @return  The decoded search filter.
648   *
649   * @throws  DirectoryException  If a problem occurs while attempting
650   *                              to decode the provided string as a
651   *                              search filter.
652   */
653  private static SearchFilter createFilterFromString(
654                                   String filterString, int startPos,
655                                   int endPos)
656          throws DirectoryException
657  {
658    // Make sure that the length is sufficient for a valid search
659    // filter.
660    int length = endPos - startPos;
661    if (length <= 0)
662    {
663      LocalizableMessage message = ERR_SEARCH_FILTER_NULL.get();
664      throw new DirectoryException(
665              ResultCode.PROTOCOL_ERROR, message);
666    }
667
668
669    // If the filter is surrounded by parentheses (which it should
670    // be), then strip them off.
671    if (filterString.charAt(startPos) == '(')
672    {
673      if (filterString.charAt(endPos-1) == ')')
674      {
675        startPos++;
676        endPos--;
677      }
678      else
679      {
680        LocalizableMessage message = ERR_SEARCH_FILTER_MISMATCHED_PARENTHESES.
681            get(filterString, startPos, endPos);
682        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
683                                     message);
684      }
685    }
686
687
688    // Look at the first character.  If it is a '&' then it is an AND
689    // search.  If it is a '|' then it is an OR search.  If it is a
690    // '!' then it is a NOT search.
691    char c = filterString.charAt(startPos);
692    if (c == '&')
693    {
694      return decodeCompoundFilter(FilterType.AND, filterString,
695                                  startPos+1, endPos);
696    }
697    else if (c == '|')
698    {
699      return decodeCompoundFilter(FilterType.OR, filterString,
700                                  startPos+1, endPos);
701    }
702    else if (c == '!')
703    {
704      return decodeCompoundFilter(FilterType.NOT, filterString,
705                                  startPos+1, endPos);
706    }
707
708
709    // If we've gotten here, then it must be a simple filter.  It must
710    // have an equal sign at some point, so find it.
711    int equalPos = -1;
712    for (int i=startPos; i < endPos; i++)
713    {
714      if (filterString.charAt(i) == '=')
715      {
716        equalPos = i;
717        break;
718      }
719    }
720
721    if (equalPos <= startPos)
722    {
723      LocalizableMessage message = ERR_SEARCH_FILTER_NO_EQUAL_SIGN.get(
724          filterString, startPos, endPos);
725      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
726                                   message);
727    }
728
729
730    // Look at the character immediately before the equal sign,
731    // because it may help determine the filter type.
732    int attrEndPos;
733    FilterType filterType;
734    switch (filterString.charAt(equalPos-1))
735    {
736      case '~':
737        filterType = FilterType.APPROXIMATE_MATCH;
738        attrEndPos = equalPos-1;
739        break;
740      case '>':
741        filterType = FilterType.GREATER_OR_EQUAL;
742        attrEndPos = equalPos-1;
743        break;
744      case '<':
745        filterType = FilterType.LESS_OR_EQUAL;
746        attrEndPos = equalPos-1;
747        break;
748      case ':':
749        return decodeExtensibleMatchFilter(filterString, startPos,
750                                           equalPos, endPos);
751      default:
752        filterType = FilterType.EQUALITY;
753        attrEndPos = equalPos;
754        break;
755    }
756
757
758    // The part of the filter string before the equal sign should be
759    // the attribute type (with or without options).  Decode it.
760    String attrType = filterString.substring(startPos, attrEndPos);
761    StringBuilder lowerType = new StringBuilder(attrType.length());
762    Set<String> attributeOptions = new HashSet<>();
763
764    int semicolonPos = attrType.indexOf(';');
765    if (semicolonPos < 0)
766    {
767      for (int i=0; i < attrType.length(); i++)
768      {
769        lowerType.append(Character.toLowerCase(attrType.charAt(i)));
770      }
771    }
772    else
773    {
774      for (int i=0; i < semicolonPos; i++)
775      {
776        lowerType.append(Character.toLowerCase(attrType.charAt(i)));
777      }
778
779      int nextPos = attrType.indexOf(';', semicolonPos+1);
780      while (nextPos > 0)
781      {
782        attributeOptions.add(attrType.substring(semicolonPos+1,
783                                                nextPos));
784        semicolonPos = nextPos;
785        nextPos = attrType.indexOf(';', semicolonPos+1);
786      }
787
788      attributeOptions.add(attrType.substring(semicolonPos+1));
789    }
790
791    // Get the attribute value.
792    AttributeType attributeType = getAttributeType(attrType, lowerType);
793    String valueStr = filterString.substring(equalPos+1, endPos);
794    if (valueStr.length() == 0)
795    {
796      return new SearchFilter(filterType, null, null, attributeType,
797                    attributeOptions, ByteString.empty(),
798                    null, null, null, null, false);
799    }
800    else if (valueStr.equals("*"))
801    {
802      return new SearchFilter(FilterType.PRESENT, null, null,
803                              attributeType, attributeOptions, null,
804                              null, null, null, null, false);
805    }
806    else if (valueStr.indexOf('*') >= 0)
807    {
808      return decodeSubstringFilter(filterString, attributeType,
809                                   attributeOptions, equalPos,
810                                   endPos);
811    }
812    else
813    {
814      boolean hasEscape = false;
815      byte[] valueBytes = getBytes(valueStr);
816      for (byte valueByte : valueBytes)
817      {
818        if (valueByte == 0x5C) // The backslash character
819        {
820          hasEscape = true;
821          break;
822        }
823      }
824
825      ByteString userValue;
826      if (hasEscape)
827      {
828        ByteStringBuilder valueBuffer =
829            new ByteStringBuilder(valueStr.length());
830        for (int i=0; i < valueBytes.length; i++)
831        {
832          if (valueBytes[i] == 0x5C) // The backslash character
833          {
834            // The next two bytes must be the hex characters that
835            // comprise the binary value.
836            if (i + 2 >= valueBytes.length)
837            {
838              LocalizableMessage message =
839                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
840                    get(filterString, equalPos+i+1);
841              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
842                                           message);
843            }
844
845            byte byteValue = 0;
846            switch (valueBytes[++i])
847            {
848              case 0x30: // '0'
849                break;
850              case 0x31: // '1'
851                byteValue = (byte) 0x10;
852                break;
853              case 0x32: // '2'
854                byteValue = (byte) 0x20;
855                break;
856              case 0x33: // '3'
857                byteValue = (byte) 0x30;
858                break;
859              case 0x34: // '4'
860                byteValue = (byte) 0x40;
861                break;
862              case 0x35: // '5'
863                byteValue = (byte) 0x50;
864                break;
865              case 0x36: // '6'
866                byteValue = (byte) 0x60;
867                break;
868              case 0x37: // '7'
869                byteValue = (byte) 0x70;
870                break;
871              case 0x38: // '8'
872                byteValue = (byte) 0x80;
873                break;
874              case 0x39: // '9'
875                byteValue = (byte) 0x90;
876                break;
877              case 0x41: // 'A'
878              case 0x61: // 'a'
879                byteValue = (byte) 0xA0;
880                break;
881              case 0x42: // 'B'
882              case 0x62: // 'b'
883                byteValue = (byte) 0xB0;
884                break;
885              case 0x43: // 'C'
886              case 0x63: // 'c'
887                byteValue = (byte) 0xC0;
888                break;
889              case 0x44: // 'D'
890              case 0x64: // 'd'
891                byteValue = (byte) 0xD0;
892                break;
893              case 0x45: // 'E'
894              case 0x65: // 'e'
895                byteValue = (byte) 0xE0;
896                break;
897              case 0x46: // 'F'
898              case 0x66: // 'f'
899                byteValue = (byte) 0xF0;
900                break;
901              default:
902                LocalizableMessage message =
903                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
904                      get(filterString, equalPos+i+1);
905                throw new DirectoryException(
906                               ResultCode.PROTOCOL_ERROR, message);
907            }
908
909            switch (valueBytes[++i])
910            {
911              case 0x30: // '0'
912                break;
913              case 0x31: // '1'
914                byteValue |= (byte) 0x01;
915                break;
916              case 0x32: // '2'
917                byteValue |= (byte) 0x02;
918                break;
919              case 0x33: // '3'
920                byteValue |= (byte) 0x03;
921                break;
922              case 0x34: // '4'
923                byteValue |= (byte) 0x04;
924                break;
925              case 0x35: // '5'
926                byteValue |= (byte) 0x05;
927                break;
928              case 0x36: // '6'
929                byteValue |= (byte) 0x06;
930                break;
931              case 0x37: // '7'
932                byteValue |= (byte) 0x07;
933                break;
934              case 0x38: // '8'
935                byteValue |= (byte) 0x08;
936                break;
937              case 0x39: // '9'
938                byteValue |= (byte) 0x09;
939                break;
940              case 0x41: // 'A'
941              case 0x61: // 'a'
942                byteValue |= (byte) 0x0A;
943                break;
944              case 0x42: // 'B'
945              case 0x62: // 'b'
946                byteValue |= (byte) 0x0B;
947                break;
948              case 0x43: // 'C'
949              case 0x63: // 'c'
950                byteValue |= (byte) 0x0C;
951                break;
952              case 0x44: // 'D'
953              case 0x64: // 'd'
954                byteValue |= (byte) 0x0D;
955                break;
956              case 0x45: // 'E'
957              case 0x65: // 'e'
958                byteValue |= (byte) 0x0E;
959                break;
960              case 0x46: // 'F'
961              case 0x66: // 'f'
962                byteValue |= (byte) 0x0F;
963                break;
964              default:
965                LocalizableMessage message =
966                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
967                      get(filterString, equalPos+i+1);
968                throw new DirectoryException(
969                               ResultCode.PROTOCOL_ERROR, message);
970            }
971
972            valueBuffer.appendByte(byteValue);
973          }
974          else
975          {
976            valueBuffer.appendByte(valueBytes[i]);
977          }
978        }
979
980        userValue = valueBuffer.toByteString();
981      }
982      else
983      {
984        userValue = ByteString.wrap(valueBytes);
985      }
986
987      return new SearchFilter(filterType, null, null, attributeType,
988                              attributeOptions, userValue, null, null,
989                              null, null, false);
990    }
991  }
992
993
994
995  /**
996   * Decodes a set of filters from the provided filter string within
997   * the indicated range.
998   *
999   * @param  filterType    The filter type for this compound filter.
1000   *                       It must be an AND, OR or NOT filter.
1001   * @param  filterString  The string containing the filter
1002   *                       information to decode.
1003   * @param  startPos      The position of the first character in the
1004   *                       set of filters to decode.
1005   * @param  endPos        The position of the first character after
1006   *                       the end of the set of filters to decode.
1007   *
1008   * @return  The decoded search filter.
1009   *
1010   * @throws  DirectoryException  If a problem occurs while attempting
1011   *                              to decode the compound filter.
1012   */
1013  private static SearchFilter decodeCompoundFilter(
1014                                   FilterType filterType,
1015                                   String filterString, int startPos,
1016                                   int endPos)
1017          throws DirectoryException
1018  {
1019    // Create a list to hold the returned components.
1020    List<SearchFilter> filterComponents = new ArrayList<>();
1021
1022
1023    // If the end pos is equal to the start pos, then there are no components.
1024    if (startPos == endPos)
1025    {
1026      if (filterType == FilterType.NOT)
1027      {
1028        LocalizableMessage message = ERR_SEARCH_FILTER_NOT_EXACTLY_ONE.get(
1029            filterString, startPos, endPos);
1030        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
1031      }
1032      else
1033      {
1034        // This is valid and will be treated as a TRUE/FALSE filter.
1035        return new SearchFilter(filterType, filterComponents, null,
1036                                null, null, null, null, null, null,
1037                                null, false);
1038      }
1039    }
1040
1041
1042    // The first and last characters must be parentheses.  If not,
1043    // then that's an error.
1044    if (filterString.charAt(startPos) != '(' ||
1045        filterString.charAt(endPos-1) != ')')
1046    {
1047      LocalizableMessage message =
1048          ERR_SEARCH_FILTER_COMPOUND_MISSING_PARENTHESES.
1049            get(filterString, startPos, endPos);
1050      throw new DirectoryException(
1051              ResultCode.PROTOCOL_ERROR, message);
1052    }
1053
1054
1055    // Iterate through the characters in the value.  Whenever an open
1056    // parenthesis is found, locate the corresponding close
1057    // parenthesis by counting the number of intermediate open/close
1058    // parentheses.
1059    int pendingOpens = 0;
1060    int openPos = -1;
1061    for (int i=startPos; i < endPos; i++)
1062    {
1063      char c = filterString.charAt(i);
1064      if (c == '(')
1065      {
1066        if (openPos < 0)
1067        {
1068          openPos = i;
1069        }
1070
1071        pendingOpens++;
1072      }
1073      else if (c == ')')
1074      {
1075        pendingOpens--;
1076        if (pendingOpens == 0)
1077        {
1078          filterComponents.add(createFilterFromString(filterString,
1079                                                      openPos, i+1));
1080          openPos = -1;
1081        }
1082        else if (pendingOpens < 0)
1083        {
1084          LocalizableMessage message =
1085              ERR_SEARCH_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS.
1086                get(filterString, i);
1087          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1088                                       message);
1089        }
1090      }
1091      else if (pendingOpens <= 0)
1092      {
1093        LocalizableMessage message =
1094            ERR_SEARCH_FILTER_COMPOUND_MISSING_PARENTHESES.
1095              get(filterString, startPos, endPos);
1096        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1097                                     message);
1098      }
1099    }
1100
1101
1102    // At this point, we have parsed the entire set of filter
1103    // components.  The list of open parenthesis positions must be
1104    // empty.
1105    if (pendingOpens != 0)
1106    {
1107      LocalizableMessage message =
1108          ERR_SEARCH_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS.
1109            get(filterString, openPos);
1110      throw new DirectoryException(
1111              ResultCode.PROTOCOL_ERROR, message);
1112    }
1113
1114
1115    // We should have everything we need, so return the list.
1116    if (filterType == FilterType.NOT)
1117    {
1118      if (filterComponents.size() != 1)
1119      {
1120        LocalizableMessage message = ERR_SEARCH_FILTER_NOT_EXACTLY_ONE.get(
1121            filterString, startPos, endPos);
1122        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1123                                     message);
1124      }
1125      SearchFilter notComponent = filterComponents.get(0);
1126      return new SearchFilter(filterType, null, notComponent, null,
1127                              null, null, null, null, null, null,
1128                              false);
1129    }
1130    else
1131    {
1132      return new SearchFilter(filterType, filterComponents, null,
1133                              null, null, null, null, null, null,
1134                              null, false);
1135    }
1136  }
1137
1138
1139  /**
1140   * Decodes a substring search filter component based on the provided
1141   * information.
1142   *
1143   * @param  filterString  The filter string containing the
1144   *                       information to decode.
1145   * @param  attrType      The attribute type for this substring
1146   *                       filter component.
1147   * @param  options       The set of attribute options for the
1148   *                       associated attribute type.
1149   * @param  equalPos      The location of the equal sign separating
1150   *                       the attribute type from the value.
1151   * @param  endPos        The position of the first character after
1152   *                       the end of the substring value.
1153   *
1154   * @return  The decoded search filter.
1155   *
1156   * @throws  DirectoryException  If a problem occurs while attempting
1157   *                              to decode the substring filter.
1158   */
1159  private static SearchFilter decodeSubstringFilter(
1160                                   String filterString,
1161                                   AttributeType attrType,
1162                                   Set<String> options, int equalPos,
1163                                   int endPos)
1164          throws DirectoryException
1165  {
1166    // Get a binary representation of the value.
1167    byte[] valueBytes =
1168         getBytes(filterString.substring(equalPos+1, endPos));
1169
1170
1171    // Find the locations of all the asterisks in the value.  Also,
1172    // check to see if there are any escaped values, since they will
1173    // need special treatment.
1174    boolean hasEscape = false;
1175    LinkedList<Integer> asteriskPositions = new LinkedList<>();
1176    for (int i=0; i < valueBytes.length; i++)
1177    {
1178      if (valueBytes[i] == 0x2A) // The asterisk.
1179      {
1180        asteriskPositions.add(i);
1181      }
1182      else if (valueBytes[i] == 0x5C) // The backslash.
1183      {
1184        hasEscape = true;
1185      }
1186    }
1187
1188
1189    // If there were no asterisks, then this isn't a substring filter.
1190    if (asteriskPositions.isEmpty())
1191    {
1192      LocalizableMessage message = ERR_SEARCH_FILTER_SUBSTRING_NO_ASTERISKS.get(
1193          filterString, equalPos+1, endPos);
1194      throw new DirectoryException(
1195              ResultCode.PROTOCOL_ERROR, message);
1196    }
1197    else
1198    {
1199      // The rest of the processing will be only on the value bytes,
1200      // so re-adjust the end position.
1201      endPos = valueBytes.length;
1202    }
1203
1204
1205    // If the value starts with an asterisk, then there is no
1206    // subInitial component.  Otherwise, parse out the subInitial.
1207    ByteString subInitial;
1208    int firstPos = asteriskPositions.removeFirst();
1209    if (firstPos == 0)
1210    {
1211      subInitial = null;
1212    }
1213    else
1214    {
1215      if (hasEscape)
1216      {
1217        ByteStringBuilder buffer = new ByteStringBuilder(firstPos);
1218        for (int i=0; i < firstPos; i++)
1219        {
1220          if (valueBytes[i] == 0x5C)
1221          {
1222            // The next two bytes must be the hex characters that
1223            // comprise the binary value.
1224            if (i + 2 >= valueBytes.length)
1225            {
1226              LocalizableMessage message =
1227                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1228                    get(filterString, equalPos+i+1);
1229              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1230                                           message);
1231            }
1232
1233            byte byteValue = 0;
1234            switch (valueBytes[++i])
1235            {
1236              case 0x30: // '0'
1237                break;
1238              case 0x31: // '1'
1239                byteValue = (byte) 0x10;
1240                break;
1241              case 0x32: // '2'
1242                byteValue = (byte) 0x20;
1243                break;
1244              case 0x33: // '3'
1245                byteValue = (byte) 0x30;
1246                break;
1247              case 0x34: // '4'
1248                byteValue = (byte) 0x40;
1249                break;
1250              case 0x35: // '5'
1251                byteValue = (byte) 0x50;
1252                break;
1253              case 0x36: // '6'
1254                byteValue = (byte) 0x60;
1255                break;
1256              case 0x37: // '7'
1257                byteValue = (byte) 0x70;
1258                break;
1259              case 0x38: // '8'
1260                byteValue = (byte) 0x80;
1261                break;
1262              case 0x39: // '9'
1263                byteValue = (byte) 0x90;
1264                break;
1265              case 0x41: // 'A'
1266              case 0x61: // 'a'
1267                byteValue = (byte) 0xA0;
1268                break;
1269              case 0x42: // 'B'
1270              case 0x62: // 'b'
1271                byteValue = (byte) 0xB0;
1272                break;
1273              case 0x43: // 'C'
1274              case 0x63: // 'c'
1275                byteValue = (byte) 0xC0;
1276                break;
1277              case 0x44: // 'D'
1278              case 0x64: // 'd'
1279                byteValue = (byte) 0xD0;
1280                break;
1281              case 0x45: // 'E'
1282              case 0x65: // 'e'
1283                byteValue = (byte) 0xE0;
1284                break;
1285              case 0x46: // 'F'
1286              case 0x66: // 'f'
1287                byteValue = (byte) 0xF0;
1288                break;
1289              default:
1290                LocalizableMessage message =
1291                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1292                      get(filterString, equalPos+i+1);
1293                throw new DirectoryException(
1294                               ResultCode.PROTOCOL_ERROR, message);
1295            }
1296
1297            switch (valueBytes[++i])
1298            {
1299              case 0x30: // '0'
1300                break;
1301              case 0x31: // '1'
1302                byteValue |= (byte) 0x01;
1303                break;
1304              case 0x32: // '2'
1305                byteValue |= (byte) 0x02;
1306                break;
1307              case 0x33: // '3'
1308                byteValue |= (byte) 0x03;
1309                break;
1310              case 0x34: // '4'
1311                byteValue |= (byte) 0x04;
1312                break;
1313              case 0x35: // '5'
1314                byteValue |= (byte) 0x05;
1315                break;
1316              case 0x36: // '6'
1317                byteValue |= (byte) 0x06;
1318                break;
1319              case 0x37: // '7'
1320                byteValue |= (byte) 0x07;
1321                break;
1322              case 0x38: // '8'
1323                byteValue |= (byte) 0x08;
1324                break;
1325              case 0x39: // '9'
1326                byteValue |= (byte) 0x09;
1327                break;
1328              case 0x41: // 'A'
1329              case 0x61: // 'a'
1330                byteValue |= (byte) 0x0A;
1331                break;
1332              case 0x42: // 'B'
1333              case 0x62: // 'b'
1334                byteValue |= (byte) 0x0B;
1335                break;
1336              case 0x43: // 'C'
1337              case 0x63: // 'c'
1338                byteValue |= (byte) 0x0C;
1339                break;
1340              case 0x44: // 'D'
1341              case 0x64: // 'd'
1342                byteValue |= (byte) 0x0D;
1343                break;
1344              case 0x45: // 'E'
1345              case 0x65: // 'e'
1346                byteValue |= (byte) 0x0E;
1347                break;
1348              case 0x46: // 'F'
1349              case 0x66: // 'f'
1350                byteValue |= (byte) 0x0F;
1351                break;
1352              default:
1353                LocalizableMessage message =
1354                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1355                      get(filterString, equalPos+i+1);
1356                throw new DirectoryException(
1357                               ResultCode.PROTOCOL_ERROR, message);
1358            }
1359
1360            buffer.appendByte(byteValue);
1361          }
1362          else
1363          {
1364            buffer.appendByte(valueBytes[i]);
1365          }
1366        }
1367
1368        subInitial = buffer.toByteString();
1369      }
1370      else
1371      {
1372        subInitial = ByteString.wrap(valueBytes, 0, firstPos);
1373      }
1374    }
1375
1376
1377    // Next, process through the rest of the asterisks to get the subAny values.
1378    List<ByteString> subAny = new ArrayList<>();
1379    for (int asteriskPos : asteriskPositions)
1380    {
1381      int length = asteriskPos - firstPos - 1;
1382
1383      if (hasEscape)
1384      {
1385        ByteStringBuilder buffer = new ByteStringBuilder(length);
1386        for (int i=firstPos+1; i < asteriskPos; i++)
1387        {
1388          if (valueBytes[i] == 0x5C)
1389          {
1390            // The next two bytes must be the hex characters that
1391            // comprise the binary value.
1392            if (i + 2 >= valueBytes.length)
1393            {
1394              LocalizableMessage message =
1395                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1396                    get(filterString, equalPos+i+1);
1397              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1398                                           message);
1399            }
1400
1401            byte byteValue = 0;
1402            switch (valueBytes[++i])
1403            {
1404              case 0x30: // '0'
1405                break;
1406              case 0x31: // '1'
1407                byteValue = (byte) 0x10;
1408                break;
1409              case 0x32: // '2'
1410                byteValue = (byte) 0x20;
1411                break;
1412              case 0x33: // '3'
1413                byteValue = (byte) 0x30;
1414                break;
1415              case 0x34: // '4'
1416                byteValue = (byte) 0x40;
1417                break;
1418              case 0x35: // '5'
1419                byteValue = (byte) 0x50;
1420                break;
1421              case 0x36: // '6'
1422                byteValue = (byte) 0x60;
1423                break;
1424              case 0x37: // '7'
1425                byteValue = (byte) 0x70;
1426                break;
1427              case 0x38: // '8'
1428                byteValue = (byte) 0x80;
1429                break;
1430              case 0x39: // '9'
1431                byteValue = (byte) 0x90;
1432                break;
1433              case 0x41: // 'A'
1434              case 0x61: // 'a'
1435                byteValue = (byte) 0xA0;
1436                break;
1437              case 0x42: // 'B'
1438              case 0x62: // 'b'
1439                byteValue = (byte) 0xB0;
1440                break;
1441              case 0x43: // 'C'
1442              case 0x63: // 'c'
1443                byteValue = (byte) 0xC0;
1444                break;
1445              case 0x44: // 'D'
1446              case 0x64: // 'd'
1447                byteValue = (byte) 0xD0;
1448                break;
1449              case 0x45: // 'E'
1450              case 0x65: // 'e'
1451                byteValue = (byte) 0xE0;
1452                break;
1453              case 0x46: // 'F'
1454              case 0x66: // 'f'
1455                byteValue = (byte) 0xF0;
1456                break;
1457              default:
1458                LocalizableMessage message =
1459                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1460                      get(filterString, equalPos+i+1);
1461                throw new DirectoryException(
1462                               ResultCode.PROTOCOL_ERROR, message);
1463            }
1464
1465            switch (valueBytes[++i])
1466            {
1467              case 0x30: // '0'
1468                break;
1469              case 0x31: // '1'
1470                byteValue |= (byte) 0x01;
1471                break;
1472              case 0x32: // '2'
1473                byteValue |= (byte) 0x02;
1474                break;
1475              case 0x33: // '3'
1476                byteValue |= (byte) 0x03;
1477                break;
1478              case 0x34: // '4'
1479                byteValue |= (byte) 0x04;
1480                break;
1481              case 0x35: // '5'
1482                byteValue |= (byte) 0x05;
1483                break;
1484              case 0x36: // '6'
1485                byteValue |= (byte) 0x06;
1486                break;
1487              case 0x37: // '7'
1488                byteValue |= (byte) 0x07;
1489                break;
1490              case 0x38: // '8'
1491                byteValue |= (byte) 0x08;
1492                break;
1493              case 0x39: // '9'
1494                byteValue |= (byte) 0x09;
1495                break;
1496              case 0x41: // 'A'
1497              case 0x61: // 'a'
1498                byteValue |= (byte) 0x0A;
1499                break;
1500              case 0x42: // 'B'
1501              case 0x62: // 'b'
1502                byteValue |= (byte) 0x0B;
1503                break;
1504              case 0x43: // 'C'
1505              case 0x63: // 'c'
1506                byteValue |= (byte) 0x0C;
1507                break;
1508              case 0x44: // 'D'
1509              case 0x64: // 'd'
1510                byteValue |= (byte) 0x0D;
1511                break;
1512              case 0x45: // 'E'
1513              case 0x65: // 'e'
1514                byteValue |= (byte) 0x0E;
1515                break;
1516              case 0x46: // 'F'
1517              case 0x66: // 'f'
1518                byteValue |= (byte) 0x0F;
1519                break;
1520              default:
1521                LocalizableMessage message =
1522                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1523                      get(filterString, equalPos+i+1);
1524                throw new DirectoryException(
1525                               ResultCode.PROTOCOL_ERROR, message);
1526            }
1527
1528            buffer.appendByte(byteValue);
1529          }
1530          else
1531          {
1532            buffer.appendByte(valueBytes[i]);
1533          }
1534        }
1535
1536        subAny.add(buffer.toByteString());
1537        buffer.clear();
1538      }
1539      else
1540      {
1541        subAny.add(ByteString.wrap(valueBytes, firstPos+1, length));
1542      }
1543
1544
1545      firstPos = asteriskPos;
1546    }
1547
1548
1549    // Finally, see if there is anything after the last asterisk,
1550    // which would be the subFinal value.
1551    ByteString subFinal;
1552    if (firstPos == (endPos-1))
1553    {
1554      subFinal = null;
1555    }
1556    else
1557    {
1558      int length = endPos - firstPos - 1;
1559
1560      if (hasEscape)
1561      {
1562        ByteStringBuilder buffer = new ByteStringBuilder(length);
1563        for (int i=firstPos+1; i < endPos; i++)
1564        {
1565          if (valueBytes[i] == 0x5C)
1566          {
1567            // The next two bytes must be the hex characters that
1568            // comprise the binary value.
1569            if (i + 2 >= valueBytes.length)
1570            {
1571              LocalizableMessage message =
1572                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1573                    get(filterString, equalPos+i+1);
1574              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1575                                           message);
1576            }
1577
1578            byte byteValue = 0;
1579            switch (valueBytes[++i])
1580            {
1581              case 0x30: // '0'
1582                break;
1583              case 0x31: // '1'
1584                byteValue = (byte) 0x10;
1585                break;
1586              case 0x32: // '2'
1587                byteValue = (byte) 0x20;
1588                break;
1589              case 0x33: // '3'
1590                byteValue = (byte) 0x30;
1591                break;
1592              case 0x34: // '4'
1593                byteValue = (byte) 0x40;
1594                break;
1595              case 0x35: // '5'
1596                byteValue = (byte) 0x50;
1597                break;
1598              case 0x36: // '6'
1599                byteValue = (byte) 0x60;
1600                break;
1601              case 0x37: // '7'
1602                byteValue = (byte) 0x70;
1603                break;
1604              case 0x38: // '8'
1605                byteValue = (byte) 0x80;
1606                break;
1607              case 0x39: // '9'
1608                byteValue = (byte) 0x90;
1609                break;
1610              case 0x41: // 'A'
1611              case 0x61: // 'a'
1612                byteValue = (byte) 0xA0;
1613                break;
1614              case 0x42: // 'B'
1615              case 0x62: // 'b'
1616                byteValue = (byte) 0xB0;
1617                break;
1618              case 0x43: // 'C'
1619              case 0x63: // 'c'
1620                byteValue = (byte) 0xC0;
1621                break;
1622              case 0x44: // 'D'
1623              case 0x64: // 'd'
1624                byteValue = (byte) 0xD0;
1625                break;
1626              case 0x45: // 'E'
1627              case 0x65: // 'e'
1628                byteValue = (byte) 0xE0;
1629                break;
1630              case 0x46: // 'F'
1631              case 0x66: // 'f'
1632                byteValue = (byte) 0xF0;
1633                break;
1634              default:
1635                LocalizableMessage message =
1636                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1637                      get(filterString, equalPos+i+1);
1638                throw new DirectoryException(
1639                               ResultCode.PROTOCOL_ERROR, message);
1640            }
1641
1642            switch (valueBytes[++i])
1643            {
1644              case 0x30: // '0'
1645                break;
1646              case 0x31: // '1'
1647                byteValue |= (byte) 0x01;
1648                break;
1649              case 0x32: // '2'
1650                byteValue |= (byte) 0x02;
1651                break;
1652              case 0x33: // '3'
1653                byteValue |= (byte) 0x03;
1654                break;
1655              case 0x34: // '4'
1656                byteValue |= (byte) 0x04;
1657                break;
1658              case 0x35: // '5'
1659                byteValue |= (byte) 0x05;
1660                break;
1661              case 0x36: // '6'
1662                byteValue |= (byte) 0x06;
1663                break;
1664              case 0x37: // '7'
1665                byteValue |= (byte) 0x07;
1666                break;
1667              case 0x38: // '8'
1668                byteValue |= (byte) 0x08;
1669                break;
1670              case 0x39: // '9'
1671                byteValue |= (byte) 0x09;
1672                break;
1673              case 0x41: // 'A'
1674              case 0x61: // 'a'
1675                byteValue |= (byte) 0x0A;
1676                break;
1677              case 0x42: // 'B'
1678              case 0x62: // 'b'
1679                byteValue |= (byte) 0x0B;
1680                break;
1681              case 0x43: // 'C'
1682              case 0x63: // 'c'
1683                byteValue |= (byte) 0x0C;
1684                break;
1685              case 0x44: // 'D'
1686              case 0x64: // 'd'
1687                byteValue |= (byte) 0x0D;
1688                break;
1689              case 0x45: // 'E'
1690              case 0x65: // 'e'
1691                byteValue |= (byte) 0x0E;
1692                break;
1693              case 0x46: // 'F'
1694              case 0x66: // 'f'
1695                byteValue |= (byte) 0x0F;
1696                break;
1697              default:
1698                LocalizableMessage message =
1699                    ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1700                      get(filterString, equalPos+i+1);
1701                throw new DirectoryException(
1702                               ResultCode.PROTOCOL_ERROR, message);
1703            }
1704
1705            buffer.appendByte(byteValue);
1706          }
1707          else
1708          {
1709            buffer.appendByte(valueBytes[i]);
1710          }
1711        }
1712
1713        subFinal = buffer.toByteString();
1714      }
1715      else
1716      {
1717        subFinal = ByteString.wrap(valueBytes, firstPos+1, length);
1718      }
1719    }
1720
1721
1722    return new SearchFilter(FilterType.SUBSTRING, null, null,
1723                            attrType, options, null, subInitial,
1724                            subAny, subFinal, null, false);
1725  }
1726
1727
1728
1729  /**
1730   * Decodes an extensible match filter component based on the
1731   * provided information.
1732   *
1733   * @param  filterString  The filter string containing the
1734   *                       information to decode.
1735   * @param  startPos      The position in the filter string of the
1736   *                       first character in the extensible match
1737   *                       filter.
1738   * @param  equalPos      The position of the equal sign in the
1739   *                       extensible match filter.
1740   * @param  endPos        The position of the first character after
1741   *                       the end of the extensible match filter.
1742   *
1743   * @return  The decoded search filter.
1744   *
1745   * @throws  DirectoryException  If a problem occurs while attempting
1746   *                              to decode the extensible match
1747   *                              filter.
1748   */
1749  private static SearchFilter decodeExtensibleMatchFilter(
1750                                   String filterString, int startPos,
1751                                   int equalPos, int endPos)
1752          throws DirectoryException
1753  {
1754    AttributeType attributeType    = null;
1755    Set<String>   attributeOptions = new HashSet<>();
1756    boolean       dnAttributes     = false;
1757    String        matchingRuleID   = null;
1758
1759
1760    // Look at the first character.  If it is a colon, then it must be
1761    // followed by either the string "dn" or the matching rule ID.  If
1762    // it is not, then it must be the attribute type.
1763    String lowerLeftStr =
1764         toLowerCase(filterString.substring(startPos, equalPos));
1765    if (filterString.charAt(startPos) == ':')
1766    {
1767      // See if it starts with ":dn".  Otherwise, it much be the
1768      // matching rule
1769      // ID.
1770      if (lowerLeftStr.startsWith(":dn:"))
1771      {
1772        dnAttributes = true;
1773
1774        matchingRuleID =
1775             filterString.substring(startPos+4, equalPos-1);
1776      }
1777      else
1778      {
1779        matchingRuleID =
1780             filterString.substring(startPos+1, equalPos-1);
1781      }
1782    }
1783    else
1784    {
1785      int colonPos = filterString.indexOf(':',startPos);
1786      if (colonPos < 0)
1787      {
1788        LocalizableMessage message = ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_COLON.
1789            get(filterString, startPos);
1790        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1791                                     message);
1792      }
1793
1794
1795      String attrType = filterString.substring(startPos, colonPos);
1796      StringBuilder lowerType = new StringBuilder(attrType.length());
1797
1798      int semicolonPos = attrType.indexOf(';');
1799      if (semicolonPos <0)
1800      {
1801        for (int i=0; i < attrType.length(); i++)
1802        {
1803          lowerType.append(Character.toLowerCase(attrType.charAt(i)));
1804        }
1805      }
1806      else
1807      {
1808        for (int i=0; i < semicolonPos; i++)
1809        {
1810          lowerType.append(Character.toLowerCase(attrType.charAt(i)));
1811        }
1812
1813        int nextPos = attrType.indexOf(';', semicolonPos+1);
1814        while (nextPos > 0)
1815        {
1816          attributeOptions.add(attrType.substring(semicolonPos+1,
1817                                                  nextPos));
1818          semicolonPos = nextPos;
1819          nextPos = attrType.indexOf(';', semicolonPos+1);
1820        }
1821
1822        attributeOptions.add(attrType.substring(semicolonPos+1));
1823      }
1824
1825
1826      // Get the attribute type for the specified name.
1827      attributeType = getAttributeType(attrType, lowerType);
1828
1829      // If there is anything left, then it should be ":dn" and/or ":"
1830      // followed by the matching rule ID.
1831      if (colonPos < equalPos-1)
1832      {
1833        if (lowerLeftStr.startsWith(":dn:", colonPos))
1834        {
1835          dnAttributes = true;
1836
1837          if (colonPos+4 < equalPos-1)
1838          {
1839            matchingRuleID =
1840                 filterString.substring(colonPos+4, equalPos-1);
1841          }
1842        }
1843        else
1844        {
1845          matchingRuleID =
1846               filterString.substring(colonPos+1, equalPos-1);
1847        }
1848      }
1849    }
1850
1851
1852    // Parse out the attribute value.
1853    byte[] valueBytes = getBytes(filterString.substring(equalPos+1,
1854                                                        endPos));
1855    boolean hasEscape = false;
1856    for (byte valueByte : valueBytes)
1857    {
1858      if (valueByte == 0x5C)
1859      {
1860        hasEscape = true;
1861        break;
1862      }
1863    }
1864
1865    ByteString userValue;
1866    if (hasEscape)
1867    {
1868      ByteStringBuilder valueBuffer =
1869          new ByteStringBuilder(valueBytes.length);
1870      for (int i=0; i < valueBytes.length; i++)
1871      {
1872        if (valueBytes[i] == 0x5C) // The backslash character
1873        {
1874          // The next two bytes must be the hex characters that
1875          // comprise the binary value.
1876          if (i + 2 >= valueBytes.length)
1877          {
1878            LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1879                get(filterString, equalPos+i+1);
1880            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1881                                         message);
1882          }
1883
1884          byte byteValue = 0;
1885          switch (valueBytes[++i])
1886          {
1887            case 0x30: // '0'
1888              break;
1889            case 0x31: // '1'
1890              byteValue = (byte) 0x10;
1891              break;
1892            case 0x32: // '2'
1893              byteValue = (byte) 0x20;
1894              break;
1895            case 0x33: // '3'
1896              byteValue = (byte) 0x30;
1897              break;
1898            case 0x34: // '4'
1899              byteValue = (byte) 0x40;
1900              break;
1901            case 0x35: // '5'
1902              byteValue = (byte) 0x50;
1903              break;
1904            case 0x36: // '6'
1905              byteValue = (byte) 0x60;
1906              break;
1907            case 0x37: // '7'
1908              byteValue = (byte) 0x70;
1909              break;
1910            case 0x38: // '8'
1911              byteValue = (byte) 0x80;
1912              break;
1913            case 0x39: // '9'
1914              byteValue = (byte) 0x90;
1915              break;
1916            case 0x41: // 'A'
1917            case 0x61: // 'a'
1918              byteValue = (byte) 0xA0;
1919              break;
1920            case 0x42: // 'B'
1921            case 0x62: // 'b'
1922              byteValue = (byte) 0xB0;
1923              break;
1924            case 0x43: // 'C'
1925            case 0x63: // 'c'
1926              byteValue = (byte) 0xC0;
1927              break;
1928            case 0x44: // 'D'
1929            case 0x64: // 'd'
1930              byteValue = (byte) 0xD0;
1931              break;
1932            case 0x45: // 'E'
1933            case 0x65: // 'e'
1934              byteValue = (byte) 0xE0;
1935              break;
1936            case 0x46: // 'F'
1937            case 0x66: // 'f'
1938              byteValue = (byte) 0xF0;
1939              break;
1940            default:
1941              LocalizableMessage message =
1942                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
1943                    get(filterString, equalPos+i+1);
1944              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1945                                           message);
1946          }
1947
1948          switch (valueBytes[++i])
1949          {
1950            case 0x30: // '0'
1951              break;
1952            case 0x31: // '1'
1953              byteValue |= (byte) 0x01;
1954              break;
1955            case 0x32: // '2'
1956              byteValue |= (byte) 0x02;
1957              break;
1958            case 0x33: // '3'
1959              byteValue |= (byte) 0x03;
1960              break;
1961            case 0x34: // '4'
1962              byteValue |= (byte) 0x04;
1963              break;
1964            case 0x35: // '5'
1965              byteValue |= (byte) 0x05;
1966              break;
1967            case 0x36: // '6'
1968              byteValue |= (byte) 0x06;
1969              break;
1970            case 0x37: // '7'
1971              byteValue |= (byte) 0x07;
1972              break;
1973            case 0x38: // '8'
1974              byteValue |= (byte) 0x08;
1975              break;
1976            case 0x39: // '9'
1977              byteValue |= (byte) 0x09;
1978              break;
1979            case 0x41: // 'A'
1980            case 0x61: // 'a'
1981              byteValue |= (byte) 0x0A;
1982              break;
1983            case 0x42: // 'B'
1984            case 0x62: // 'b'
1985              byteValue |= (byte) 0x0B;
1986              break;
1987            case 0x43: // 'C'
1988            case 0x63: // 'c'
1989              byteValue |= (byte) 0x0C;
1990              break;
1991            case 0x44: // 'D'
1992            case 0x64: // 'd'
1993              byteValue |= (byte) 0x0D;
1994              break;
1995            case 0x45: // 'E'
1996            case 0x65: // 'e'
1997              byteValue |= (byte) 0x0E;
1998              break;
1999            case 0x46: // 'F'
2000            case 0x66: // 'f'
2001              byteValue |= (byte) 0x0F;
2002              break;
2003            default:
2004              LocalizableMessage message =
2005                  ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE.
2006                    get(filterString, equalPos+i+1);
2007              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
2008                                           message);
2009          }
2010
2011          valueBuffer.appendByte(byteValue);
2012        }
2013        else
2014        {
2015          valueBuffer.appendByte(valueBytes[i]);
2016        }
2017      }
2018
2019      userValue = valueBuffer.toByteString();
2020    }
2021    else
2022    {
2023      userValue = ByteString.wrap(valueBytes);
2024    }
2025
2026    // Make sure that the filter contains at least one of an attribute
2027    // type or a matching rule ID.  Also, construct the appropriate
2028    // attribute  value.
2029    if (attributeType == null)
2030    {
2031      if (matchingRuleID == null)
2032      {
2033        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
2034            ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR.get(filterString, startPos));
2035      }
2036
2037      MatchingRule mr = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID));
2038      if (mr == null)
2039      {
2040        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
2041            ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_SUCH_MR.get(filterString, startPos, matchingRuleID));
2042      }
2043    }
2044
2045    return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
2046                            attributeType, attributeOptions, userValue,
2047                            null, null, null, matchingRuleID,
2048                            dnAttributes);
2049  }
2050
2051  private static AttributeType getAttributeType(String attrType, StringBuilder lowerType)
2052  {
2053    AttributeType attributeType = DirectoryServer.getAttributeType(lowerType.toString());
2054    if (attributeType.isPlaceHolder())
2055    {
2056      String typeStr = attrType.substring(0, lowerType.length());
2057      attributeType = DirectoryServer.getAttributeType(typeStr);
2058    }
2059    return attributeType;
2060  }
2061
2062  /**
2063   * Retrieves the filter type for this search filter.
2064   *
2065   * @return  The filter type for this search filter.
2066   */
2067  public FilterType getFilterType()
2068  {
2069    return filterType;
2070  }
2071
2072
2073
2074  /**
2075   * Retrieves the set of filter components for this AND or OR filter.
2076   * The returned list can be modified by the caller.
2077   *
2078   * @return  The set of filter components for this AND or OR filter.
2079   */
2080  public Set<SearchFilter> getFilterComponents()
2081  {
2082    return filterComponents;
2083  }
2084
2085
2086
2087  /**
2088   * Retrieves the filter component for this NOT filter.
2089   *
2090   * @return  The filter component for this NOT filter, or
2091   *          <CODE>null</CODE> if this is not a NOT filter.
2092   */
2093  public SearchFilter getNotComponent()
2094  {
2095    return notComponent;
2096  }
2097
2098
2099
2100  /**
2101   * Retrieves the attribute type for this filter.
2102   *
2103   * @return  The attribute type for this filter, or <CODE>null</CODE>
2104   *          if there is none.
2105   */
2106  public AttributeType getAttributeType()
2107  {
2108    return attributeType;
2109  }
2110
2111
2112
2113  /**
2114   * Retrieves the assertion value for this filter.
2115   *
2116   * @return  The assertion value for this filter, or
2117   *          <CODE>null</CODE> if there is none.
2118   */
2119  public ByteString getAssertionValue()
2120  {
2121    return assertionValue;
2122  }
2123
2124  /**
2125   * Retrieves the subInitial element for this substring filter.
2126   *
2127   * @return  The subInitial element for this substring filter, or
2128   *          <CODE>null</CODE> if there is none.
2129   */
2130  public ByteString getSubInitialElement()
2131  {
2132    return subInitialElement;
2133  }
2134
2135
2136
2137  /**
2138   * Retrieves the set of subAny elements for this substring filter.
2139   * The returned list may be altered by the caller.
2140   *
2141   * @return  The set of subAny elements for this substring filter.
2142   */
2143  public List<ByteString> getSubAnyElements()
2144  {
2145    return subAnyElements;
2146  }
2147
2148
2149
2150  /**
2151   * Retrieves the subFinal element for this substring filter.
2152   *
2153   * @return  The subFinal element for this substring filter.
2154   */
2155  public ByteString getSubFinalElement()
2156  {
2157    return subFinalElement;
2158  }
2159
2160
2161
2162  /**
2163   * Retrieves the matching rule ID for this extensible matching
2164   * filter.
2165   *
2166   * @return  The matching rule ID for this extensible matching
2167   *          filter.
2168   */
2169  public String getMatchingRuleID()
2170  {
2171    return matchingRuleID;
2172  }
2173
2174
2175
2176  /**
2177   * Retrieves the dnAttributes flag for this extensible matching
2178   * filter.
2179   *
2180   * @return  The dnAttributes flag for this extensible matching
2181   *          filter.
2182   */
2183  public boolean getDNAttributes()
2184  {
2185    return dnAttributes;
2186  }
2187
2188
2189
2190  /**
2191   * Indicates whether this search filter matches the provided entry.
2192   *
2193   * @param  entry  The entry for which to make the determination.
2194   *
2195   * @return  <CODE>true</CODE> if this search filter matches the
2196   *          provided entry, or <CODE>false</CODE> if it does not.
2197   *
2198   * @throws  DirectoryException  If a problem is encountered during
2199   *                              processing.
2200   */
2201  public boolean matchesEntry(Entry entry)
2202         throws DirectoryException
2203  {
2204    ConditionResult result = matchesEntryInternal(this, entry, 0);
2205    switch (result)
2206    {
2207      case TRUE:
2208        return true;
2209      case FALSE:
2210      case UNDEFINED:
2211        return false;
2212      default:
2213        logger.error(ERR_SEARCH_FILTER_INVALID_RESULT_TYPE, entry.getName(), this, result);
2214        return false;
2215    }
2216  }
2217
2218
2219
2220  /**
2221   * Indicates whether the this filter matches the provided entry.
2222   *
2223   * @param  completeFilter  The complete filter being checked, of
2224   *                         which this filter may be a subset.
2225   * @param  entry           The entry for which to make the
2226   *                         determination.
2227   * @param  depth           The current depth of the evaluation,
2228   *                         which is used to prevent infinite
2229   *                         recursion due to highly nested filters
2230   *                         and eventually running out of stack
2231   *                         space.
2232   *
2233   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2234   *          entry, <CODE>FALSE</CODE> if it does not, or
2235   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2236   *
2237   * @throws  DirectoryException  If a problem is encountered during
2238   *                              processing.
2239   */
2240  private ConditionResult matchesEntryInternal(
2241                               SearchFilter completeFilter,
2242                               Entry entry, int depth)
2243          throws DirectoryException
2244  {
2245    switch (filterType)
2246    {
2247      case AND:
2248        return processAND(completeFilter, entry, depth);
2249
2250      case OR:
2251        return processOR(completeFilter, entry, depth);
2252
2253      case NOT:
2254        return processNOT(completeFilter, entry, depth);
2255
2256      case EQUALITY:
2257        return processEquality(completeFilter, entry);
2258
2259      case SUBSTRING:
2260        return processSubstring(completeFilter, entry);
2261
2262      case GREATER_OR_EQUAL:
2263        return processGreaterOrEqual(completeFilter, entry);
2264
2265      case LESS_OR_EQUAL:
2266        return processLessOrEqual(completeFilter, entry);
2267
2268      case PRESENT:
2269        return processPresent(completeFilter, entry);
2270
2271      case APPROXIMATE_MATCH:
2272        return processApproximate(completeFilter, entry);
2273
2274      case EXTENSIBLE_MATCH:
2275        return processExtensibleMatch(completeFilter, entry);
2276
2277
2278      default:
2279        // This is an invalid filter type.
2280        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
2281            ERR_SEARCH_FILTER_INVALID_FILTER_TYPE.get(entry.getName(), this, filterType));
2282    }
2283  }
2284
2285
2286
2287  /**
2288   * Indicates whether the this AND filter matches the provided entry.
2289   *
2290   * @param  completeFilter  The complete filter being checked, of
2291   *                         which this filter may be a subset.
2292   * @param  entry           The entry for which to make the
2293   *                         determination.
2294   * @param  depth           The current depth of the evaluation,
2295   *                         which is used to prevent infinite
2296   *                         recursion due to highly nested filters
2297   *                         and eventually running out of stack
2298   *                         space.
2299   *
2300   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2301   *          entry, <CODE>FALSE</CODE> if it does not, or
2302   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2303   *
2304   * @throws  DirectoryException  If a problem is encountered during
2305   *                              processing.
2306   */
2307  private ConditionResult processAND(SearchFilter completeFilter,
2308                                     Entry entry, int depth)
2309          throws DirectoryException
2310  {
2311    if (filterComponents == null)
2312    {
2313      // The set of subcomponents was null.  This is not allowed.
2314      LocalizableMessage message =
2315          ERR_SEARCH_FILTER_COMPOUND_COMPONENTS_NULL.
2316            get(entry.getName(), completeFilter, filterType);
2317      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
2318    }
2319    else if (filterComponents.isEmpty())
2320    {
2321      // An AND filter with no elements like "(&)" is specified as
2322      // "undefined" in RFC 2251, but is considered one of the
2323      // TRUE/FALSE filters in RFC 4526, in which case we should
2324      // always return true.
2325      if (logger.isTraceEnabled())
2326      {
2327        logger.trace("Returning TRUE for LDAP TRUE " +
2328            "filter (&)");
2329      }
2330      return ConditionResult.TRUE;
2331    }
2332    else
2333    {
2334      // We will have to evaluate one or more subcomponents.  In
2335      // this case, first check our depth to make sure we're not
2336      // nesting too deep.
2337      if (depth >= MAX_NESTED_FILTER_DEPTH)
2338      {
2339        LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP.
2340            get(entry.getName(), completeFilter);
2341        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
2342      }
2343
2344      for (SearchFilter f : filterComponents)
2345      {
2346        ConditionResult result =
2347             f.matchesEntryInternal(completeFilter, entry, depth + 1);
2348        switch (result)
2349        {
2350          case TRUE:
2351            break;
2352          case FALSE:
2353            if (logger.isTraceEnabled())
2354            {
2355              logger.trace(
2356                  "Returning FALSE for AND component %s in " +
2357                  "filter %s for entry %s",
2358                           f, completeFilter, entry.getName());
2359            }
2360            return result;
2361          case UNDEFINED:
2362            if (logger.isTraceEnabled())
2363            {
2364              logger.trace(
2365             "Undefined result for AND component %s in filter " +
2366             "%s for entry %s", f, completeFilter, entry.getName());
2367            }
2368            return result;
2369          default:
2370            LocalizableMessage message =
2371                ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
2372                  get(entry.getName(), completeFilter, result);
2373            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
2374        }
2375      }
2376
2377      // If we have gotten here, then all the components must have
2378      // matched.
2379      if (logger.isTraceEnabled())
2380      {
2381        logger.trace(
2382            "Returning TRUE for AND component %s in filter %s " +
2383            "for entry %s", this, completeFilter, entry.getName());
2384      }
2385      return ConditionResult.TRUE;
2386    }
2387  }
2388
2389
2390
2391  /**
2392   * Indicates whether the this OR filter matches the provided entry.
2393   *
2394   * @param  completeFilter  The complete filter being checked, of
2395   *                         which this filter may be a subset.
2396   * @param  entry           The entry for which to make the
2397   *                         determination.
2398   * @param  depth           The current depth of the evaluation,
2399   *                         which is used to prevent infinite
2400   *                         recursion due to highly nested filters
2401   *                         and eventually running out of stack
2402   *                         space.
2403   *
2404   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2405   *          entry, <CODE>FALSE</CODE> if it does not, or
2406   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2407   *
2408   * @throws  DirectoryException  If a problem is encountered during
2409   *                              processing.
2410   */
2411  private ConditionResult processOR(SearchFilter completeFilter,
2412                                    Entry entry, int depth)
2413          throws DirectoryException
2414  {
2415    if (filterComponents == null)
2416    {
2417      // The set of subcomponents was null.  This is not allowed.
2418      LocalizableMessage message =
2419          ERR_SEARCH_FILTER_COMPOUND_COMPONENTS_NULL.
2420            get(entry.getName(), completeFilter, filterType);
2421      throw new DirectoryException(
2422                     DirectoryServer.getServerErrorResultCode(),
2423                     message);
2424    }
2425    else if (filterComponents.isEmpty())
2426    {
2427      // An OR filter with no elements like "(|)" is specified as
2428      // "undefined" in RFC 2251, but is considered one of the
2429      // TRUE/FALSE filters in RFC 4526, in which case we should
2430      // always return false.
2431      if (logger.isTraceEnabled())
2432      {
2433        logger.trace("Returning FALSE for LDAP FALSE " +
2434            "filter (|)");
2435      }
2436      return ConditionResult.FALSE;
2437    }
2438    else
2439    {
2440      // We will have to evaluate one or more subcomponents.  In
2441      // this case, first check our depth to make sure we're not
2442      // nesting too deep.
2443      if (depth >= MAX_NESTED_FILTER_DEPTH)
2444      {
2445        LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP.
2446            get(entry.getName(), completeFilter);
2447        throw new DirectoryException(
2448                       DirectoryServer.getServerErrorResultCode(),
2449                       message);
2450      }
2451
2452      ConditionResult result = ConditionResult.FALSE;
2453      for (SearchFilter f : filterComponents)
2454      {
2455        switch (f.matchesEntryInternal(completeFilter, entry,
2456                               depth+1))
2457        {
2458          case TRUE:
2459            if (logger.isTraceEnabled())
2460            {
2461              logger.trace(
2462                "Returning TRUE for OR component %s in filter " +
2463                "%s for entry %s",
2464                f, completeFilter, entry.getName());
2465            }
2466            return ConditionResult.TRUE;
2467          case FALSE:
2468            break;
2469          case UNDEFINED:
2470            if (logger.isTraceEnabled())
2471            {
2472              logger.trace(
2473              "Undefined result for OR component %s in filter " +
2474              "%s for entry %s",
2475              f, completeFilter, entry.getName());
2476            }
2477            result = ConditionResult.UNDEFINED;
2478            break;
2479          default:
2480            LocalizableMessage message =
2481                ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
2482                  get(entry.getName(), completeFilter, result);
2483            throw new
2484                 DirectoryException(
2485                      DirectoryServer.getServerErrorResultCode(),
2486                      message);
2487        }
2488      }
2489
2490
2491      if (logger.isTraceEnabled())
2492      {
2493        logger.trace(
2494            "Returning %s for OR component %s in filter %s for " +
2495            "entry %s", result, this, completeFilter,
2496                        entry.getName());
2497      }
2498      return result;
2499    }
2500  }
2501
2502
2503
2504  /**
2505   * Indicates whether the this NOT filter matches the provided entry.
2506   *
2507   * @param  completeFilter  The complete filter being checked, of
2508   *                         which this filter may be a subset.
2509   * @param  entry           The entry for which to make the
2510   *                         determination.
2511   * @param  depth           The current depth of the evaluation,
2512   *                         which is used to prevent infinite
2513   *                         recursion due to highly nested filters
2514   *                         and eventually running out of stack
2515   *                         space.
2516   *
2517   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2518   *          entry, <CODE>FALSE</CODE> if it does not, or
2519   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2520   *
2521   * @throws  DirectoryException  If a problem is encountered during
2522   *                              processing.
2523   */
2524  private ConditionResult processNOT(SearchFilter completeFilter,
2525                                     Entry entry, int depth)
2526          throws DirectoryException
2527  {
2528    if (notComponent == null)
2529    {
2530      // The NOT subcomponent was null.  This is not allowed.
2531      LocalizableMessage message = ERR_SEARCH_FILTER_NOT_COMPONENT_NULL.
2532          get(entry.getName(), completeFilter);
2533      throw new DirectoryException(
2534                     DirectoryServer.getServerErrorResultCode(),
2535                     message);
2536    }
2537    else
2538    {
2539      // The subcomponent for the NOT filter can be an AND, OR, or
2540      // NOT filter that would require more nesting.  Make sure
2541      // that we don't go too deep.
2542      if (depth >= MAX_NESTED_FILTER_DEPTH)
2543      {
2544        LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP.
2545            get(entry.getName(), completeFilter);
2546        throw new DirectoryException(
2547                       DirectoryServer.getServerErrorResultCode(),
2548                       message);
2549      }
2550
2551      ConditionResult result =
2552           notComponent.matchesEntryInternal(completeFilter,
2553                                             entry, depth+1);
2554      switch (result)
2555      {
2556        case TRUE:
2557          if (logger.isTraceEnabled())
2558          {
2559            logger.trace(
2560               "Returning FALSE for NOT component %s in filter " +
2561               "%s for entry %s",
2562               notComponent, completeFilter, entry.getName());
2563          }
2564          return ConditionResult.FALSE;
2565        case FALSE:
2566          if (logger.isTraceEnabled())
2567          {
2568            logger.trace(
2569                "Returning TRUE for NOT component %s in filter " +
2570                "%s for entry %s",
2571                notComponent, completeFilter, entry.getName());
2572          }
2573          return ConditionResult.TRUE;
2574        case UNDEFINED:
2575          if (logger.isTraceEnabled())
2576          {
2577            logger.trace(
2578              "Undefined result for NOT component %s in filter " +
2579              "%s for entry %s",
2580              notComponent, completeFilter, entry.getName());
2581          }
2582          return ConditionResult.UNDEFINED;
2583        default:
2584          LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
2585              get(entry.getName(), completeFilter, result);
2586          throw new
2587               DirectoryException(
2588                    DirectoryServer.getServerErrorResultCode(),
2589                    message);
2590      }
2591    }
2592  }
2593
2594
2595
2596  /**
2597   * Indicates whether the this equality filter matches the provided
2598   * entry.
2599   *
2600   * @param  completeFilter  The complete filter being checked, of
2601   *                         which this filter may be a subset.
2602   * @param  entry           The entry for which to make the
2603   *                         determination.
2604   *
2605   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2606   *          entry, <CODE>FALSE</CODE> if it does not, or
2607   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2608   *
2609   * @throws  DirectoryException  If a problem is encountered during
2610   *                              processing.
2611   */
2612  private ConditionResult processEquality(SearchFilter completeFilter,
2613                                          Entry entry)
2614          throws DirectoryException
2615  {
2616    // Make sure that an attribute type has been defined.
2617    if (attributeType == null)
2618    {
2619      LocalizableMessage message =
2620          ERR_SEARCH_FILTER_EQUALITY_NO_ATTRIBUTE_TYPE.
2621            get(entry.getName(), toString());
2622      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2623    }
2624
2625    // Make sure that an assertion value has been defined.
2626    if (assertionValue == null)
2627    {
2628      LocalizableMessage message =
2629          ERR_SEARCH_FILTER_EQUALITY_NO_ASSERTION_VALUE.
2630            get(entry.getName(), toString(), attributeType.getNameOrOID());
2631      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2632    }
2633
2634    // See if the entry has an attribute with the requested type.
2635    List<Attribute> attrs = entry.getAttribute(attributeDescription);
2636    if (attrs.isEmpty())
2637    {
2638      if (logger.isTraceEnabled())
2639      {
2640        logger.trace(
2641            "Returning FALSE for equality component %s in " +
2642            "filter %s because entry %s didn't have attribute " +
2643            "type %s",
2644                     this, completeFilter, entry.getName(),
2645                     attributeType.getNameOrOID());
2646      }
2647      return ConditionResult.FALSE;
2648    }
2649
2650    // Get the equality matching rule for the given attribute type
2651    MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
2652    if (matchingRule == null)
2653    {
2654      if (logger.isTraceEnabled())
2655      {
2656        logger.trace(
2657         "Attribute type %s does not have an equality matching " +
2658         "rule -- returning undefined.",
2659         attributeType.getNameOrOID());
2660      }
2661      return ConditionResult.UNDEFINED;
2662    }
2663
2664    // Iterate through all the attributes and see if we can find a match.
2665    ConditionResult result = ConditionResult.FALSE;
2666    for (Attribute a : attrs)
2667    {
2668      final ConditionResult cr = a.matchesEqualityAssertion(assertionValue);
2669      if (cr == ConditionResult.TRUE)
2670      {
2671        if (logger.isTraceEnabled())
2672        {
2673          logger.trace(
2674              "Returning TRUE for equality component %s in filter %s " +
2675                  "for entry %s", this, completeFilter, entry.getName());
2676        }
2677        return ConditionResult.TRUE;
2678      }
2679      else if (cr == ConditionResult.UNDEFINED)
2680      {
2681        result = ConditionResult.UNDEFINED;
2682      }
2683    }
2684
2685    if (logger.isTraceEnabled())
2686    {
2687      logger.trace(
2688          "Returning %s for equality component %s in filter %s " +
2689              "because entry %s didn't have attribute type %s with value %s",
2690          result, this, completeFilter, entry.getName(), attributeType.getNameOrOID(), assertionValue);
2691    }
2692    return result;
2693  }
2694
2695
2696
2697  /**
2698   * Indicates whether the this substring filter matches the provided
2699   * entry.
2700   *
2701   * @param  completeFilter  The complete filter being checked, of
2702   *                         which this filter may be a subset.
2703   * @param  entry           The entry for which to make the
2704   *                         determination.
2705   *
2706   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2707   *          entry, <CODE>FALSE</CODE> if it does not, or
2708   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2709   *
2710   * @throws  DirectoryException  If a problem is encountered during
2711   *                              processing.
2712   */
2713  private ConditionResult processSubstring(
2714                                SearchFilter completeFilter,
2715                                Entry entry)
2716          throws DirectoryException
2717  {
2718    // Make sure that an attribute type has been defined.
2719    if (attributeType == null)
2720    {
2721      LocalizableMessage message =
2722          ERR_SEARCH_FILTER_SUBSTRING_NO_ATTRIBUTE_TYPE.
2723            get(entry.getName(), toString());
2724      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2725    }
2726
2727    // Make sure that at least one substring element has been defined.
2728    if (subInitialElement == null &&
2729        subFinalElement == null &&
2730        (subAnyElements == null || subAnyElements.isEmpty()))
2731    {
2732      LocalizableMessage message =
2733          ERR_SEARCH_FILTER_SUBSTRING_NO_SUBSTRING_COMPONENTS.
2734            get(entry.getName(), toString(), attributeType.getNameOrOID());
2735      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2736    }
2737
2738    // See if the entry has an attribute with the requested type.
2739    List<Attribute> attrs = entry.getAttribute(attributeDescription);
2740    if (attrs.isEmpty())
2741    {
2742      if (logger.isTraceEnabled())
2743      {
2744        logger.trace(
2745            "Returning FALSE for substring component %s in " +
2746            "filter %s because entry %s didn't have attribute " +
2747            "type %s",
2748                     this, completeFilter, entry.getName(),
2749                     attributeType.getNameOrOID());
2750      }
2751      return ConditionResult.FALSE;
2752    }
2753
2754    // Iterate through all the attributes and see if we can find a
2755    // match.
2756    ConditionResult result = ConditionResult.FALSE;
2757    for (Attribute a : attrs)
2758    {
2759      switch (a.matchesSubstring(subInitialElement,
2760                                 subAnyElements,
2761                                 subFinalElement))
2762      {
2763        case TRUE:
2764          if (logger.isTraceEnabled())
2765          {
2766            logger.trace(
2767                "Returning TRUE for substring component %s in " +
2768                "filter %s for entry %s",
2769                         this, completeFilter, entry.getName());
2770          }
2771          return ConditionResult.TRUE;
2772        case FALSE:
2773          break;
2774        case UNDEFINED:
2775          if (logger.isTraceEnabled())
2776          {
2777            logger.trace(
2778                "Undefined result encountered for substring " +
2779                "component %s in filter %s for entry %s",
2780                         this, completeFilter, entry.getName());
2781          }
2782          result = ConditionResult.UNDEFINED;
2783          break;
2784        default:
2785      }
2786    }
2787
2788    if (logger.isTraceEnabled())
2789    {
2790      logger.trace(
2791          "Returning %s for substring component %s in filter " +
2792          "%s for entry %s",
2793          result, this, completeFilter, entry.getName());
2794    }
2795    return result;
2796  }
2797
2798
2799
2800  /**
2801   * Indicates whether the this greater-or-equal filter matches the
2802   * provided entry.
2803   *
2804   * @param  completeFilter  The complete filter being checked, of
2805   *                         which this filter may be a subset.
2806   * @param  entry           The entry for which to make the
2807   *                         determination.
2808   *
2809   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2810   *          entry, <CODE>FALSE</CODE> if it does not, or
2811   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2812   *
2813   * @throws  DirectoryException  If a problem is encountered during
2814   *                              processing.
2815   */
2816  private ConditionResult processGreaterOrEqual(
2817                                SearchFilter completeFilter,
2818                                Entry entry)
2819          throws DirectoryException
2820  {
2821    // Make sure that an attribute type has been defined.
2822    if (attributeType == null)
2823    {
2824      LocalizableMessage message =
2825          ERR_SEARCH_FILTER_GREATER_OR_EQUAL_NO_ATTRIBUTE_TYPE.
2826            get(entry.getName(), toString());
2827      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2828    }
2829
2830    // Make sure that an assertion value has been defined.
2831    if (assertionValue == null)
2832    {
2833      LocalizableMessage message =
2834          ERR_SEARCH_FILTER_GREATER_OR_EQUAL_NO_VALUE.
2835            get(entry.getName(), toString(), attributeType.getNameOrOID());
2836      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2837    }
2838
2839    // See if the entry has an attribute with the requested type.
2840    List<Attribute> attrs = entry.getAttribute(attributeDescription);
2841    if (attrs.isEmpty())
2842    {
2843      if (logger.isTraceEnabled())
2844      {
2845        logger.trace("Returning FALSE for " +
2846            "greater-or-equal component %s in filter %s " +
2847            "because entry %s didn't have attribute type %s",
2848                     this, completeFilter, entry.getName(),
2849                     attributeType.getNameOrOID());
2850      }
2851      return ConditionResult.FALSE;
2852    }
2853
2854    // Iterate through all the attributes and see if we can find a match.
2855    ConditionResult result = ConditionResult.FALSE;
2856    for (Attribute a : attrs)
2857    {
2858      switch (a.greaterThanOrEqualTo(assertionValue))
2859      {
2860        case TRUE:
2861          if (logger.isTraceEnabled())
2862          {
2863            logger.trace(
2864                "Returning TRUE for greater-or-equal component " +
2865                "%s in filter %s for entry %s",
2866                         this, completeFilter, entry.getName());
2867          }
2868          return ConditionResult.TRUE;
2869        case FALSE:
2870          break;
2871        case UNDEFINED:
2872          if (logger.isTraceEnabled())
2873          {
2874            logger.trace(
2875                "Undefined result encountered for " +
2876                "greater-or-equal component %s in filter %s " +
2877                "for entry %s", this, completeFilter,
2878                entry.getName());
2879          }
2880          result = ConditionResult.UNDEFINED;
2881          break;
2882        default:
2883      }
2884    }
2885
2886    if (logger.isTraceEnabled())
2887    {
2888      logger.trace(
2889          "Returning %s for greater-or-equal component %s in " +
2890          "filter %s for entry %s",
2891                   result, this, completeFilter, entry.getName());
2892    }
2893    return result;
2894  }
2895
2896
2897
2898  /**
2899   * Indicates whether the this less-or-equal filter matches the
2900   * provided entry.
2901   *
2902   * @param  completeFilter  The complete filter being checked, of
2903   *                         which this filter may be a subset.
2904   * @param  entry           The entry for which to make the
2905   *                         determination.
2906   *
2907   * @return  <CODE>TRUE</CODE> if this filter matches the provided
2908   *          entry, <CODE>FALSE</CODE> if it does not, or
2909   *          <CODE>UNDEFINED</CODE> if the result is undefined.
2910   *
2911   * @throws  DirectoryException  If a problem is encountered during
2912   *                              processing.
2913   */
2914  private ConditionResult processLessOrEqual(
2915                                SearchFilter completeFilter,
2916                                Entry entry)
2917          throws DirectoryException
2918  {
2919    // Make sure that an attribute type has been defined.
2920    if (attributeType == null)
2921    {
2922      LocalizableMessage message =
2923          ERR_SEARCH_FILTER_LESS_OR_EQUAL_NO_ATTRIBUTE_TYPE.
2924            get(entry.getName(), toString());
2925      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2926    }
2927
2928    // Make sure that an assertion value has been defined.
2929    if (assertionValue == null)
2930    {
2931      LocalizableMessage message =
2932          ERR_SEARCH_FILTER_LESS_OR_EQUAL_NO_ASSERTION_VALUE.
2933            get(entry.getName(), toString(), attributeType.getNameOrOID());
2934      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
2935    }
2936
2937    // See if the entry has an attribute with the requested type.
2938    List<Attribute> attrs = entry.getAttribute(attributeDescription);
2939    if (attrs.isEmpty())
2940    {
2941      if (logger.isTraceEnabled())
2942      {
2943        logger.trace(
2944            "Returning FALSE for less-or-equal component %s in " +
2945            "filter %s because entry %s didn't have attribute " +
2946            "type %s", this, completeFilter, entry.getName(),
2947                       attributeType.getNameOrOID());
2948      }
2949      return ConditionResult.FALSE;
2950    }
2951
2952    // Iterate through all the attributes and see if we can find a
2953    // match.
2954    ConditionResult result = ConditionResult.FALSE;
2955    for (Attribute a : attrs)
2956    {
2957      switch (a.lessThanOrEqualTo(assertionValue))
2958      {
2959        case TRUE:
2960          if (logger.isTraceEnabled())
2961          {
2962            logger.trace(
2963                "Returning TRUE for less-or-equal component %s " +
2964                "in filter %s for entry %s",
2965                         this, completeFilter, entry.getName());
2966          }
2967          return ConditionResult.TRUE;
2968        case FALSE:
2969          break;
2970        case UNDEFINED:
2971          if (logger.isTraceEnabled())
2972          {
2973            logger.trace(
2974                "Undefined result encountered for " +
2975                    "less-or-equal component %s in filter %s " +
2976                    "for entry %s",
2977                    this, completeFilter, entry.getName());
2978          }
2979          result = ConditionResult.UNDEFINED;
2980          break;
2981        default:
2982      }
2983    }
2984
2985    if (logger.isTraceEnabled())
2986    {
2987      logger.trace(
2988          "Returning %s for less-or-equal component %s in " +
2989          "filter %s for entry %s",
2990                   result, this, completeFilter, entry.getName());
2991    }
2992    return result;
2993  }
2994
2995
2996
2997  /**
2998   * Indicates whether the this present filter matches the provided
2999   * entry.
3000   *
3001   * @param  completeFilter  The complete filter being checked, of
3002   *                         which this filter may be a subset.
3003   * @param  entry           The entry for which to make the
3004   *                         determination.
3005   *
3006   * @return  <CODE>TRUE</CODE> if this filter matches the provided
3007   *          entry, <CODE>FALSE</CODE> if it does not, or
3008   *          <CODE>UNDEFINED</CODE> if the result is undefined.
3009   *
3010   * @throws  DirectoryException  If a problem is encountered during
3011   *                              processing.
3012   */
3013  private ConditionResult processPresent(SearchFilter completeFilter,
3014                                         Entry entry)
3015          throws DirectoryException
3016  {
3017    // Make sure that an attribute type has been defined.
3018    if (attributeType == null)
3019    {
3020      LocalizableMessage message =
3021          ERR_SEARCH_FILTER_PRESENCE_NO_ATTRIBUTE_TYPE.
3022            get(entry.getName(), toString());
3023      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
3024    }
3025
3026
3027    // See if the entry has an attribute with the requested type.
3028    // If so, then it's a match.  If not, then it's not a match.
3029    ConditionResult result = ConditionResult.valueOf(entry.hasAttribute(attributeDescription));
3030    if (logger.isTraceEnabled())
3031    {
3032      logger.trace(
3033          "Returning %s for presence component %s in filter %s for entry %s",
3034          result, this, completeFilter, entry.getName());
3035    }
3036    return result;
3037  }
3038
3039
3040
3041  /**
3042   * Indicates whether the this approximate filter matches the
3043   * provided entry.
3044   *
3045   * @param  completeFilter  The complete filter being checked, of
3046   *                         which this filter may be a subset.
3047   * @param  entry           The entry for which to make the
3048   *                         determination.
3049   *
3050   * @return  <CODE>TRUE</CODE> if this filter matches the provided
3051   *          entry, <CODE>FALSE</CODE> if it does not, or
3052   *          <CODE>UNDEFINED</CODE> if the result is undefined.
3053   *
3054   * @throws  DirectoryException  If a problem is encountered during
3055   *                              processing.
3056   */
3057  private ConditionResult processApproximate(
3058                                SearchFilter completeFilter,
3059                                Entry entry)
3060          throws DirectoryException
3061  {
3062    // Make sure that an attribute type has been defined.
3063    if (attributeType == null)
3064    {
3065      LocalizableMessage message =
3066          ERR_SEARCH_FILTER_APPROXIMATE_NO_ATTRIBUTE_TYPE.
3067            get(entry.getName(), toString());
3068      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
3069    }
3070
3071    // Make sure that an assertion value has been defined.
3072    if (assertionValue == null)
3073    {
3074      LocalizableMessage message =
3075          ERR_SEARCH_FILTER_APPROXIMATE_NO_ASSERTION_VALUE.
3076            get(entry.getName(), toString(), attributeType.getNameOrOID());
3077      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
3078    }
3079
3080    // See if the entry has an attribute with the requested type.
3081    List<Attribute> attrs = entry.getAttribute(attributeDescription);
3082    if (attrs.isEmpty())
3083    {
3084      if (logger.isTraceEnabled())
3085      {
3086        logger.trace(
3087            "Returning FALSE for approximate component %s in " +
3088            "filter %s because entry %s didn't have attribute " +
3089            "type %s", this, completeFilter, entry.getName(),
3090                       attributeType.getNameOrOID());
3091      }
3092      return ConditionResult.FALSE;
3093    }
3094
3095    // Iterate through all the attributes and see if we can find a
3096    // match.
3097    ConditionResult result = ConditionResult.FALSE;
3098    for (Attribute a : attrs)
3099    {
3100      switch (a.approximatelyEqualTo(assertionValue))
3101      {
3102        case TRUE:
3103          if (logger.isTraceEnabled())
3104          {
3105            logger.trace(
3106               "Returning TRUE for approximate component %s in " +
3107               "filter %s for entry %s",
3108               this, completeFilter, entry.getName());
3109          }
3110          return ConditionResult.TRUE;
3111        case FALSE:
3112          break;
3113        case UNDEFINED:
3114          if (logger.isTraceEnabled())
3115          {
3116            logger.trace(
3117                "Undefined result encountered for approximate " +
3118                "component %s in filter %s for entry %s",
3119                         this, completeFilter, entry.getName());
3120          }
3121          result = ConditionResult.UNDEFINED;
3122          break;
3123        default:
3124      }
3125    }
3126
3127    if (logger.isTraceEnabled())
3128    {
3129      logger.trace(
3130          "Returning %s for approximate component %s in filter " +
3131          "%s for entry %s",
3132          result, this, completeFilter, entry.getName());
3133    }
3134    return result;
3135  }
3136
3137
3138
3139  /**
3140   * Indicates whether this extensibleMatch filter matches the
3141   * provided entry.
3142   *
3143   * @param  completeFilter  The complete filter in which this
3144   *                         extensibleMatch filter may be a
3145   *                         subcomponent.
3146   * @param  entry           The entry for which to make the
3147   *                         determination.
3148   *
3149   * @return <CODE>TRUE</CODE> if this extensibleMatch filter matches
3150   *         the provided entry, <CODE>FALSE</CODE> if it does not, or
3151   *         <CODE>UNDEFINED</CODE> if the result cannot be
3152   *         determined.
3153   *
3154   * @throws  DirectoryException  If a problem occurs while evaluating
3155   *                              this filter against the provided
3156   *                              entry.
3157   */
3158  private ConditionResult processExtensibleMatch(
3159                               SearchFilter completeFilter,
3160                               Entry entry)
3161          throws DirectoryException
3162  {
3163    // We must have an assertion value for which to make the
3164    // determination.
3165    if (assertionValue == null)
3166    {
3167      LocalizableMessage message =
3168          ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_ASSERTION_VALUE.
3169            get(entry.getName(), completeFilter);
3170      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
3171                                   message);
3172    }
3173
3174
3175    MatchingRule matchingRule = null;
3176
3177    if (matchingRuleID != null)
3178    {
3179      matchingRule =
3180           DirectoryServer.getMatchingRule(
3181                toLowerCase(matchingRuleID));
3182      if (matchingRule == null)
3183      {
3184        if (logger.isTraceEnabled())
3185        {
3186          logger.trace(
3187              "Unknown matching rule %s defined in extensibleMatch " +
3188              "component of filter %s -- returning undefined.",
3189                    matchingRuleID, this);
3190        }
3191        return ConditionResult.UNDEFINED;
3192      }
3193    }
3194    else
3195    {
3196      if (attributeType == null)
3197      {
3198        LocalizableMessage message =
3199            ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_RULE_OR_TYPE.
3200              get(entry.getName(), completeFilter);
3201        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
3202                                     message);
3203      }
3204      else
3205      {
3206        matchingRule = attributeType.getEqualityMatchingRule();
3207        if (matchingRule == null)
3208        {
3209          if (logger.isTraceEnabled())
3210          {
3211            logger.trace(
3212             "Attribute type %s does not have an equality matching " +
3213             "rule -- returning undefined.",
3214             attributeType.getNameOrOID());
3215          }
3216          return ConditionResult.UNDEFINED;
3217        }
3218      }
3219    }
3220
3221
3222    // If there is an attribute type, then check to see if there is a
3223    // corresponding matching rule use for the matching rule and
3224    // determine if it allows that attribute type.
3225    if (attributeType != null)
3226    {
3227      MatchingRuleUse mru =
3228           DirectoryServer.getMatchingRuleUse(matchingRule);
3229      if (mru != null && !mru.appliesToAttribute(attributeType))
3230      {
3231        if (logger.isTraceEnabled())
3232        {
3233          logger.trace(
3234              "Attribute type %s is not allowed for use with " +
3235              "matching rule %s because of matching rule use " +
3236              "definition %s", attributeType.getNameOrOID(),
3237              matchingRule.getNameOrOID(), mru.getNameOrOID());
3238        }
3239        return ConditionResult.UNDEFINED;
3240      }
3241    }
3242
3243
3244    // Normalize the assertion value using the matching rule.
3245    Assertion assertion;
3246    try
3247    {
3248      assertion = matchingRule.getAssertion(assertionValue);
3249    }
3250    catch (Exception e)
3251    {
3252      logger.traceException(e);
3253
3254      // We can't normalize the assertion value, so the result must be
3255      // undefined.
3256      return ConditionResult.UNDEFINED;
3257    }
3258
3259
3260    // If there is an attribute type, then we should only check for
3261    // that attribute.  Otherwise, we should check against all
3262    // attributes in the entry.
3263    ConditionResult result = ConditionResult.FALSE;
3264    if (attributeType == null)
3265    {
3266      for (List<Attribute> attrList :
3267           entry.getUserAttributes().values())
3268      {
3269        for (Attribute a : attrList)
3270        {
3271          for (ByteString v : a)
3272          {
3273            try
3274            {
3275              ByteString nv = matchingRule.normalizeAttributeValue(v);
3276              ConditionResult r = assertion.matches(nv);
3277              switch (r)
3278              {
3279                case TRUE:
3280                  return ConditionResult.TRUE;
3281                case FALSE:
3282                  break;
3283                case UNDEFINED:
3284                  result = ConditionResult.UNDEFINED;
3285                  break;
3286                default:
3287                  LocalizableMessage message =
3288                      ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
3289                        get(entry.getName(), completeFilter, r);
3290                  throw new DirectoryException(
3291                                 ResultCode.PROTOCOL_ERROR, message);
3292              }
3293            }
3294            catch (Exception e)
3295            {
3296              logger.traceException(e);
3297
3298              // We couldn't normalize one of the values.  If we don't
3299              // find a definite match, then we should return
3300              // undefined.
3301              result = ConditionResult.UNDEFINED;
3302            }
3303          }
3304        }
3305      }
3306
3307      for (List<Attribute> attrList :
3308           entry.getOperationalAttributes().values())
3309      {
3310        for (Attribute a : attrList)
3311        {
3312          for (ByteString v : a)
3313          {
3314            try
3315            {
3316              ByteString nv = matchingRule.normalizeAttributeValue(v);
3317              ConditionResult r = assertion.matches(nv);
3318              switch (r)
3319              {
3320                case TRUE:
3321                  return ConditionResult.TRUE;
3322                case FALSE:
3323                  break;
3324                case UNDEFINED:
3325                  result = ConditionResult.UNDEFINED;
3326                  break;
3327                default:
3328                  LocalizableMessage message =
3329                      ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
3330                        get(entry.getName(), completeFilter, r);
3331                  throw new DirectoryException(
3332                                 ResultCode.PROTOCOL_ERROR, message);
3333              }
3334            }
3335            catch (Exception e)
3336            {
3337              logger.traceException(e);
3338
3339              // We couldn't normalize one of the values.  If we don't
3340              // find a definite match, then we should return
3341              // undefined.
3342              result = ConditionResult.UNDEFINED;
3343            }
3344          }
3345        }
3346      }
3347
3348      Attribute a = entry.getObjectClassAttribute();
3349      for (ByteString v : a)
3350      {
3351        try
3352        {
3353          ByteString nv = matchingRule.normalizeAttributeValue(v);
3354          ConditionResult r = assertion.matches(nv);
3355          switch (r)
3356          {
3357            case TRUE:
3358              return ConditionResult.TRUE;
3359            case FALSE:
3360              break;
3361            case UNDEFINED:
3362              result = ConditionResult.UNDEFINED;
3363              break;
3364            default:
3365              LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
3366                  get(entry.getName(), completeFilter, r);
3367              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
3368                                           message);
3369          }
3370        }
3371        catch (Exception e)
3372        {
3373          logger.traceException(e);
3374
3375          // We couldn't normalize one of the values.  If we don't
3376          // find a definite match, then we should return undefined.
3377          result = ConditionResult.UNDEFINED;
3378        }
3379      }
3380    }
3381    else
3382    {
3383      for (Attribute a : entry.getAttribute(attributeDescription))
3384      {
3385        for (ByteString v : a)
3386        {
3387          try
3388          {
3389            ByteString nv = matchingRule.normalizeAttributeValue(v);
3390            ConditionResult r = assertion.matches(nv);
3391            switch (r)
3392            {
3393            case TRUE:
3394              return ConditionResult.TRUE;
3395            case FALSE:
3396              break;
3397            case UNDEFINED:
3398              result = ConditionResult.UNDEFINED;
3399              break;
3400            default:
3401              LocalizableMessage message =
3402                  ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.get(entry.getName(), completeFilter, r);
3403              throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
3404            }
3405          }
3406          catch (Exception e)
3407          {
3408            logger.traceException(e);
3409
3410            // We couldn't normalize one of the values.
3411            // If we don't find a definite match, then we should return undefined.
3412            result = ConditionResult.UNDEFINED;
3413          }
3414        }
3415      }
3416    }
3417
3418
3419    // If we've gotten here, then we know that there is no definite
3420    // match in the set of attributes.  If we should check DN
3421    // attributes, then do so.
3422    if (dnAttributes)
3423    {
3424      for (RDN rdn : entry.getName())
3425      {
3426        for (AVA ava : rdn)
3427        {
3428          try
3429          {
3430            if (attributeType == null || attributeType.equals(ava.getAttributeType()))
3431            {
3432              ByteString v = ava.getAttributeValue();
3433              ByteString nv = matchingRule.normalizeAttributeValue(v);
3434              ConditionResult r = assertion.matches(nv);
3435              switch (r)
3436              {
3437                case TRUE:
3438                  return ConditionResult.TRUE;
3439                case FALSE:
3440                  break;
3441                case UNDEFINED:
3442                  result = ConditionResult.UNDEFINED;
3443                  break;
3444                default:
3445                  LocalizableMessage message =
3446                      ERR_SEARCH_FILTER_INVALID_RESULT_TYPE.
3447                        get(entry.getName(), completeFilter, r);
3448                  throw new DirectoryException(
3449                                 ResultCode.PROTOCOL_ERROR, message);
3450              }
3451            }
3452          }
3453          catch (Exception e)
3454          {
3455            logger.traceException(e);
3456
3457            // We couldn't normalize one of the values.  If we don't
3458            // find a definite match, then we should return undefined.
3459            result = ConditionResult.UNDEFINED;
3460          }
3461        }
3462      }
3463    }
3464
3465
3466    // If we've gotten here, then there is no definitive match, so
3467    // we'll either return FALSE or UNDEFINED.
3468    return result;
3469  }
3470
3471
3472  /**
3473   * Indicates whether this search filter is equal to the provided
3474   * object.
3475   *
3476   * @param  o  The object for which to make the determination.
3477   *
3478   * @return  <CODE>true</CODE> if the provide object is equal to this
3479   *          search filter, or <CODE>false</CODE> if it is not.
3480   */
3481  @Override
3482  public boolean equals(Object o)
3483  {
3484    if (o == null)
3485    {
3486      return false;
3487    }
3488
3489    if (o == this)
3490    {
3491      return true;
3492    }
3493
3494    if (! (o instanceof SearchFilter))
3495    {
3496      return false;
3497    }
3498
3499
3500    SearchFilter f = (SearchFilter) o;
3501    if (filterType != f.filterType)
3502    {
3503      return false;
3504    }
3505
3506
3507    switch (filterType)
3508    {
3509      case AND:
3510      case OR:
3511        return andOrEqual(f);
3512      case NOT:
3513        return notComponent.equals(f.notComponent);
3514      case SUBSTRING:
3515        return substringEqual(f);
3516      case PRESENT:
3517        return attributeDescription.equals(f.attributeDescription);
3518      case EXTENSIBLE_MATCH:
3519        return extensibleEqual(f);
3520      case EQUALITY:
3521      case APPROXIMATE_MATCH:
3522      case GREATER_OR_EQUAL:
3523      case LESS_OR_EQUAL:
3524        return attributeDescription.equals(f.attributeDescription)
3525            && assertionValue.equals(f.assertionValue);
3526      default:
3527        return false;
3528    }
3529  }
3530
3531  private boolean andOrEqual(SearchFilter f)
3532  {
3533    if (filterComponents.size() != f.filterComponents.size())
3534    {
3535      return false;
3536    }
3537
3538    for (SearchFilter outerFilter : filterComponents)
3539    {
3540      if (!f.filterComponents.contains(outerFilter))
3541      {
3542        return false;
3543      }
3544    }
3545    return true;
3546  }
3547
3548  private boolean substringEqual(SearchFilter other)
3549  {
3550    if (!attributeDescription.equals(other.attributeDescription))
3551    {
3552      return false;
3553    }
3554
3555    MatchingRule rule = attributeType.getSubstringMatchingRule();
3556    if (rule == null)
3557    {
3558      return false;
3559    }
3560
3561    boolean initialCheck = subInitialElement == null ?
3562        other.subInitialElement == null : subInitialElement.equals(other.subInitialElement);
3563    if (!initialCheck)
3564    {
3565      return false;
3566    }
3567    boolean finalCheck = subFinalElement == null ?
3568        other.subFinalElement == null : subFinalElement.equals(other.subFinalElement);
3569    if (!finalCheck)
3570    {
3571      return false;
3572    }
3573    boolean anyCheck = subAnyElements == null ?
3574        other.subAnyElements == null : subAnyElements.size() == other.subAnyElements.size();
3575    if (!anyCheck)
3576    {
3577      return false;
3578    }
3579    if (subAnyElements != null)
3580    {
3581      for (int i = 0; i < subAnyElements.size(); i++)
3582      {
3583        if (! subAnyElements.get(i).equals(other.subAnyElements.get(i)))
3584        {
3585          return false;
3586        }
3587      }
3588    }
3589    return true;
3590  }
3591
3592  private boolean extensibleEqual(SearchFilter f)
3593  {
3594    if (attributeType == null)
3595    {
3596      if (f.attributeType != null)
3597      {
3598        return false;
3599      }
3600    }
3601    else if (!attributeDescription.equals(f.attributeDescription))
3602    {
3603      return false;
3604    }
3605
3606    if (dnAttributes != f.dnAttributes)
3607    {
3608      return false;
3609    }
3610
3611    if (matchingRuleID == null)
3612    {
3613      if (f.matchingRuleID != null)
3614      {
3615        return false;
3616      }
3617    }
3618    else
3619    {
3620      if (! matchingRuleID.equals(f.matchingRuleID))
3621      {
3622        return false;
3623      }
3624    }
3625
3626    if (assertionValue == null)
3627    {
3628      if (f.assertionValue != null)
3629      {
3630        return false;
3631      }
3632    }
3633    else
3634    {
3635      if (matchingRuleID == null)
3636      {
3637        if (! assertionValue.equals(f.assertionValue))
3638        {
3639          return false;
3640        }
3641      }
3642      else
3643      {
3644        MatchingRule mrule = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID));
3645        if (mrule == null)
3646        {
3647          return false;
3648        }
3649        else
3650        {
3651          try
3652          {
3653            Assertion assertion = mrule.getAssertion(f.assertionValue);
3654            return assertion.matches(mrule.normalizeAttributeValue(assertionValue)).toBoolean();
3655          }
3656          catch (Exception e)
3657          {
3658            return false;
3659          }
3660        }
3661      }
3662    }
3663
3664    return true;
3665  }
3666
3667  /**
3668   * Retrieves the hash code for this search filter.
3669   *
3670   * @return  The hash code for this search filter.
3671   */
3672  @Override
3673  public int hashCode()
3674  {
3675    switch (filterType)
3676    {
3677      case AND:
3678      case OR:
3679        int hashCode = 0;
3680
3681        for (SearchFilter filterComp : filterComponents)
3682        {
3683          hashCode += filterComp.hashCode();
3684        }
3685
3686        return hashCode;
3687      case NOT:
3688        return notComponent.hashCode();
3689      case EQUALITY:
3690        return typeAndAssertionHashCode();
3691      case SUBSTRING:
3692        return substringHashCode();
3693      case GREATER_OR_EQUAL:
3694        return typeAndAssertionHashCode();
3695      case LESS_OR_EQUAL:
3696        return typeAndAssertionHashCode();
3697      case PRESENT:
3698        return attributeType.hashCode();
3699      case APPROXIMATE_MATCH:
3700        return typeAndAssertionHashCode();
3701      case EXTENSIBLE_MATCH:
3702        return extensibleHashCode();
3703      default:
3704        return 1;
3705    }
3706  }
3707
3708
3709  /** Returns the hash code for extensible filter. */
3710  private int extensibleHashCode()
3711  {
3712    int hashCode = 0;
3713
3714    if (attributeType != null)
3715    {
3716      hashCode += attributeType.hashCode();
3717    }
3718
3719    if (dnAttributes)
3720    {
3721      hashCode++;
3722    }
3723
3724    if (matchingRuleID != null)
3725    {
3726      hashCode += matchingRuleID.hashCode();
3727    }
3728
3729    if (assertionValue != null)
3730    {
3731      hashCode += assertionValue.hashCode();
3732    }
3733    return hashCode;
3734  }
3735
3736
3737  private int typeAndAssertionHashCode()
3738  {
3739    return attributeType.hashCode() + assertionValue.hashCode();
3740  }
3741
3742  /** Returns hash code to use for substring filter. */
3743  private int substringHashCode()
3744  {
3745    int hashCode = attributeType.hashCode();
3746    if (subInitialElement != null)
3747    {
3748      hashCode += subInitialElement.hashCode();
3749    }
3750    if (subAnyElements != null)
3751    {
3752      for (ByteString e : subAnyElements)
3753      {
3754        hashCode += e.hashCode();
3755      }
3756    }
3757    if (subFinalElement != null)
3758    {
3759      hashCode += subFinalElement.hashCode();
3760    }
3761    return hashCode;
3762  }
3763
3764
3765
3766  /**
3767   * Retrieves a string representation of this search filter.
3768   *
3769   * @return  A string representation of this search filter.
3770   */
3771  @Override
3772  public String toString()
3773  {
3774    StringBuilder buffer = new StringBuilder();
3775    toString(buffer);
3776    return buffer.toString();
3777  }
3778
3779
3780
3781  /**
3782   * Appends a string representation of this search filter to the
3783   * provided buffer.
3784   *
3785   * @param  buffer  The buffer to which the information should be
3786   *                 appended.
3787   */
3788  public void toString(StringBuilder buffer)
3789  {
3790    switch (filterType)
3791    {
3792      case AND:
3793        buffer.append("(&");
3794        for (SearchFilter f : filterComponents)
3795        {
3796          f.toString(buffer);
3797        }
3798        buffer.append(")");
3799        break;
3800      case OR:
3801        buffer.append("(|");
3802        for (SearchFilter f : filterComponents)
3803        {
3804          f.toString(buffer);
3805        }
3806        buffer.append(")");
3807        break;
3808      case NOT:
3809        buffer.append("(!");
3810        notComponent.toString(buffer);
3811        buffer.append(")");
3812        break;
3813      case EQUALITY:
3814        appendEquation(buffer, "=");
3815        break;
3816      case SUBSTRING:
3817        buffer.append("(");
3818        buffer.append(attributeDescription);
3819        buffer.append("=");
3820
3821        if (subInitialElement != null)
3822        {
3823          valueToFilterString(buffer, subInitialElement);
3824        }
3825
3826        if (subAnyElements != null && !subAnyElements.isEmpty())
3827        {
3828          for (ByteString s : subAnyElements)
3829          {
3830            buffer.append("*");
3831            valueToFilterString(buffer, s);
3832          }
3833        }
3834
3835        buffer.append("*");
3836
3837        if (subFinalElement != null)
3838        {
3839          valueToFilterString(buffer, subFinalElement);
3840        }
3841
3842        buffer.append(")");
3843        break;
3844      case GREATER_OR_EQUAL:
3845        appendEquation(buffer, ">=");
3846        break;
3847      case LESS_OR_EQUAL:
3848        appendEquation(buffer, "<=");
3849        break;
3850      case PRESENT:
3851        buffer.append("(");
3852        buffer.append(attributeDescription);
3853        buffer.append("=*)");
3854        break;
3855      case APPROXIMATE_MATCH:
3856        appendEquation(buffer, "~=");
3857        break;
3858      case EXTENSIBLE_MATCH:
3859        buffer.append("(");
3860
3861        if (attributeDescription != null)
3862        {
3863          buffer.append(attributeDescription);
3864        }
3865
3866        if (dnAttributes)
3867        {
3868          buffer.append(":dn");
3869        }
3870
3871        if (matchingRuleID != null)
3872        {
3873          buffer.append(":");
3874          buffer.append(matchingRuleID);
3875        }
3876
3877        buffer.append(":=");
3878        valueToFilterString(buffer, assertionValue);
3879        buffer.append(")");
3880        break;
3881    }
3882  }
3883
3884  private void appendEquation(StringBuilder buffer, String operator)
3885  {
3886    buffer.append("(");
3887    buffer.append(attributeDescription);
3888    buffer.append(operator);
3889    valueToFilterString(buffer, assertionValue);
3890    buffer.append(")");
3891  }
3892
3893
3894  /**
3895   * Appends a properly-cleaned version of the provided value to the
3896   * given buffer so that it can be safely used in string
3897   * representations of this search filter.  The formatting changes
3898   * that may be performed will be in compliance with the
3899   * specification in RFC 2254.
3900   *
3901   * @param  buffer  The buffer to which the "safe" version of the
3902   *                 value will be appended.
3903   * @param  value   The value to be appended to the buffer.
3904   */
3905  private void valueToFilterString(StringBuilder buffer,
3906                                   ByteString value)
3907  {
3908    if (value == null)
3909    {
3910      return;
3911    }
3912
3913
3914    // Get the binary representation of the value and iterate through
3915    // it to see if there are any unsafe characters.  If there are,
3916    // then escape them and replace them with a two-digit hex
3917    // equivalent.
3918    buffer.ensureCapacity(buffer.length() + value.length());
3919    byte b;
3920    for (int i = 0; i < value.length(); i++)
3921    {
3922      b = value.byteAt(i);
3923      if (((b & 0x7F) != b) ||  // Not 7-bit clean
3924          (b <= 0x1F) ||        // Below the printable character range
3925          (b == 0x28) ||        // Open parenthesis
3926          (b == 0x29) ||        // Close parenthesis
3927          (b == 0x2A) ||        // Asterisk
3928          (b == 0x5C) ||        // Backslash
3929          (b == 0x7F))          // Delete character
3930      {
3931        buffer.append("\\");
3932        buffer.append(byteToHex(b));
3933      }
3934      else
3935      {
3936        buffer.append((char) b);
3937      }
3938    }
3939  }
3940
3941  /**
3942   * Returns the {@code objectClass} presence filter {@code (objectClass=*)}.
3943   *
3944   * @return The {@code objectClass} presence filter {@code (objectClass=*)}.
3945   */
3946  public static SearchFilter objectClassPresent()
3947  {
3948    if (objectClassPresent == null)
3949    {
3950      try
3951      {
3952        objectClassPresent = SearchFilter.createFilterFromString("(objectclass=*)");
3953      }
3954      catch (DirectoryException canNeverHappen)
3955      {
3956        logger.traceException(canNeverHappen);
3957      }
3958    }
3959    return objectClassPresent;
3960  }
3961}