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