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