001    /*
002     * The MIT License
003     * Copyright (c) 2012 Microsoft Corporation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a copy
006     * of this software and associated documentation files (the "Software"), to deal
007     * in the Software without restriction, including without limitation the rights
008     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009     * copies of the Software, and to permit persons to whom the Software is
010     * furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021     * THE SOFTWARE.
022     */
023    
024    package microsoft.exchange.webservices.data.search.filter;
025    
026    import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029    import microsoft.exchange.webservices.data.core.XmlAttributeNames;
030    import microsoft.exchange.webservices.data.core.XmlElementNames;
031    import microsoft.exchange.webservices.data.core.enumeration.search.ComparisonMode;
032    import microsoft.exchange.webservices.data.core.enumeration.search.ContainmentMode;
033    import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
034    import microsoft.exchange.webservices.data.core.enumeration.search.LogicalOperator;
035    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
036    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
037    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
038    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
039    import microsoft.exchange.webservices.data.misc.OutParam;
040    import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
041    import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChangedDelegate;
042    import microsoft.exchange.webservices.data.property.complex.ISearchStringProvider;
043    import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
044    import org.apache.commons.logging.Log;
045    import org.apache.commons.logging.LogFactory;
046    
047    import javax.xml.stream.XMLStreamException;
048    
049    import java.util.ArrayList;
050    import java.util.Iterator;
051    
052    /**
053     * Represents the base search filter class. Use descendant search filter classes
054     * such as SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
055     * SearchFilter.SearchFilterCollection to define search filter.
056     */
057    public abstract class SearchFilter extends ComplexProperty {
058    
059      private static final Log LOG = LogFactory.getLog(SearchFilter.class);
060    
061      /**
062       * Initializes a new instance of the SearchFilter class.
063       */
064      protected SearchFilter() {
065      }
066    
067      /**
068       * The search.
069       *
070       * @param reader the reader
071       * @return the search filter
072       * @throws Exception the exception
073       */
074      //static SearchFilter search;
075    
076      /**
077       * Loads from XML.
078       *
079       * @param reader the reader
080       * @return SearchFilter
081       * @throws Exception the exception
082       */
083      public static SearchFilter loadFromXml(EwsServiceXmlReader reader)
084          throws Exception {
085        reader.ensureCurrentNodeIsStartElement();
086    
087        SearchFilter searchFilter = null;
088    
089        if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Exists)) {
090          searchFilter = new Exists();
091        } else if (reader.getLocalName().equalsIgnoreCase(
092            XmlElementNames.Contains)) {
093          searchFilter = new ContainsSubstring();
094        } else if (reader.getLocalName().equalsIgnoreCase(
095            XmlElementNames.Excludes)) {
096          searchFilter = new ExcludesBitmask();
097        } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Not)) {
098          searchFilter = new Not();
099        } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.And)) {
100          searchFilter = new SearchFilterCollection(
101              LogicalOperator.And);
102        } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Or)) {
103          searchFilter = new SearchFilterCollection(
104              LogicalOperator.Or);
105        } else if (reader.getLocalName().equalsIgnoreCase(
106            XmlElementNames.IsEqualTo)) {
107          searchFilter = new IsEqualTo();
108        } else if (reader.getLocalName().equalsIgnoreCase(
109            XmlElementNames.IsNotEqualTo)) {
110          searchFilter = new IsNotEqualTo();
111        } else if (reader.getLocalName().equalsIgnoreCase(
112            XmlElementNames.IsGreaterThan)) {
113          searchFilter = new IsGreaterThan();
114        } else if (reader.getLocalName().equalsIgnoreCase(
115            XmlElementNames.IsGreaterThanOrEqualTo)) {
116          searchFilter = new IsGreaterThanOrEqualTo();
117        } else if (reader.getLocalName().equalsIgnoreCase(
118            XmlElementNames.IsLessThan)) {
119          searchFilter = new IsLessThan();
120        } else if (reader.getLocalName().equalsIgnoreCase(
121            XmlElementNames.IsLessThanOrEqualTo)) {
122          searchFilter = new IsLessThanOrEqualTo();
123        } else {
124          searchFilter = null;
125        }
126    
127        if (searchFilter != null) {
128          searchFilter.loadFromXml(reader, reader.getLocalName());
129        }
130    
131        return searchFilter;
132      }
133    
134      /**
135       * Gets the name of the XML element.
136       *
137       * @return the xml element name
138       */
139      protected abstract String getXmlElementName();
140    
141      /**
142       * Writes to XML.
143       *
144       * @param writer the writer
145       * @throws Exception the exception
146       */
147      public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
148        super.writeToXml(writer, this.getXmlElementName());
149      }
150    
151      /**
152       * Represents a search filter that checks for the presence of a substring
153       * inside a text property. Applications can use ContainsSubstring to define
154       * conditions such as "Field CONTAINS Value" or
155       * "Field IS PREFIXED WITH Value".
156       */
157      public static final class ContainsSubstring extends PropertyBasedFilter {
158    
159        /**
160         * The containment mode.
161         */
162        private ContainmentMode containmentMode = ContainmentMode.Substring;
163    
164        /**
165         * The comparison mode.
166         */
167        private ComparisonMode comparisonMode = ComparisonMode.IgnoreCase;
168    
169        /**
170         * The value.
171         */
172        private String value;
173    
174        /**
175         * Initializes a new instance of the class.
176         */
177        public ContainsSubstring() {
178          super();
179        }
180    
181        /**
182         * Initializes a new instance of the class.
183         *
184         * @param propertyDefinition The definition of the property that is being compared.
185         * @param value              The value to compare with.
186         */
187        public ContainsSubstring(PropertyDefinitionBase propertyDefinition,
188            String value) {
189          super(propertyDefinition);
190          this.value = value;
191        }
192    
193        /**
194         * Initializes a new instance of the class.
195         *
196         * @param propertyDefinition The definition of the property that is being compared.
197         * @param value              The value to compare with.
198         * @param containmentMode    The containment mode.
199         * @param comparisonMode     The comparison mode.
200         */
201        public ContainsSubstring(PropertyDefinitionBase propertyDefinition,
202            String value, ContainmentMode containmentMode,
203            ComparisonMode comparisonMode) {
204          this(propertyDefinition, value);
205          this.containmentMode = containmentMode;
206          this.comparisonMode = comparisonMode;
207        }
208    
209        /**
210         * validates instance.
211         *
212         * @throws ServiceValidationException the service validation exception
213         */
214        @Override
215        protected void internalValidate() throws ServiceValidationException {
216          super.internalValidate();
217          if ((this.value == null) || this.value.isEmpty()) {
218            throw new ServiceValidationException("The Value property must be set.");
219          }
220        }
221    
222        /**
223         * Gets the name of the XML element.
224         *
225         * @return the xml element name
226         */
227        @Override
228        protected String getXmlElementName() {
229          return XmlElementNames.Contains;
230        }
231    
232        /**
233         * Tries to read element from XML.
234         *
235         * @param reader the reader
236         * @return True if element was read.
237         * @throws Exception the exception
238         */
239        @Override
240        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
241            throws Exception {
242          boolean result = super.tryReadElementFromXml(reader);
243    
244          if (!result) {
245            if (reader.getLocalName().equals(XmlElementNames.Constant)) {
246              this.value = reader
247                  .readAttributeValue(XmlAttributeNames.Value);
248              result = true;
249            }
250          }
251          return result;
252        }
253    
254        /**
255         * Reads the attribute of Xml.
256         *
257         * @param reader the reader
258         * @throws Exception the exception
259         */
260        @Override
261        public void readAttributesFromXml(EwsServiceXmlReader reader)
262            throws Exception {
263    
264          super.readAttributesFromXml(reader);
265          this.containmentMode = reader.readAttributeValue(
266              ContainmentMode.class, XmlAttributeNames.ContainmentMode);
267          try {
268            this.comparisonMode = reader.readAttributeValue(
269                ComparisonMode.class,
270                XmlAttributeNames.ContainmentComparison);
271          } catch (IllegalArgumentException ile) {
272            // This will happen if we receive a value that is defined in the
273            // EWS
274            // schema but that is not defined
275            // in the API. We map that
276            // value to IgnoreCaseAndNonSpacingCharacters.
277            this.comparisonMode = ComparisonMode.
278                IgnoreCaseAndNonSpacingCharacters;
279          }
280        }
281    
282        /**
283         * Writes the attribute to XML.
284         *
285         * @param writer the writer
286         * @throws ServiceXmlSerializationException the service xml serialization exception
287         */
288        @Override
289        public void writeAttributesToXml(EwsServiceXmlWriter writer)
290            throws ServiceXmlSerializationException {
291          super.writeAttributesToXml(writer);
292    
293          writer.writeAttributeValue(XmlAttributeNames.ContainmentMode,
294              this.containmentMode);
295          writer.writeAttributeValue(XmlAttributeNames.ContainmentComparison,
296              this.comparisonMode);
297        }
298    
299        /**
300         * Writes the elements to Xml.
301         *
302         * @param writer the writer
303         * @throws XMLStreamException the XML stream exception
304         * @throws ServiceXmlSerializationException the service xml serialization exception
305         */
306        @Override
307        public void writeElementsToXml(EwsServiceXmlWriter writer)
308            throws XMLStreamException, ServiceXmlSerializationException {
309          super.writeElementsToXml(writer);
310    
311          writer.writeStartElement(XmlNamespace.Types,
312              XmlElementNames.Constant);
313          writer.writeAttributeValue(XmlAttributeNames.Value, this.value);
314          writer.writeEndElement(); // Constant
315        }
316    
317        /**
318         * Gets the containment mode.
319         *
320         * @return ContainmentMode
321         */
322        public ContainmentMode getContainmentMode() {
323          return containmentMode;
324        }
325    
326        /**
327         * sets the ContainmentMode.
328         *
329         * @param containmentMode the new containment mode
330         */
331        public void setContainmentMode(ContainmentMode containmentMode) {
332          this.containmentMode = containmentMode;
333        }
334    
335        /**
336         * Gets the comparison mode.
337         *
338         * @return ComparisonMode
339         */
340        public ComparisonMode getComparisonMode() {
341          return comparisonMode;
342        }
343    
344        /**
345         * sets the comparison mode.
346         *
347         * @param comparisonMode the new comparison mode
348         */
349        public void setComparisonMode(ComparisonMode comparisonMode) {
350          this.comparisonMode = comparisonMode;
351        }
352    
353        /**
354         * gets the value to compare the specified property with.
355         *
356         * @return String
357         */
358        public String getValue() {
359          return value;
360        }
361    
362        /**
363         * sets the value to compare the specified property with.
364         *
365         * @param value the new value
366         */
367        public void setValue(String value) {
368          this.value = value;
369        }
370      }
371    
372    
373      /**
374       * Represents a bitmask exclusion search filter. Applications can use
375       * ExcludesBitExcludesBitmaskFilter to define conditions such as
376       * "(OrdinalField and 0x0010) != 0x0010"
377       */
378      public static class ExcludesBitmask extends PropertyBasedFilter {
379    
380        /**
381         * The bitmask.
382         */
383        private int bitmask;
384    
385        /**
386         * Initializes a new instance of the class.
387         */
388        public ExcludesBitmask() {
389          super();
390        }
391    
392        /**
393         * Initializes a new instance of the class.
394         *
395         * @param propertyDefinition the property definition
396         * @param bitmask            the bitmask
397         */
398        public ExcludesBitmask(PropertyDefinitionBase propertyDefinition,
399            int bitmask) {
400          super(propertyDefinition);
401          this.bitmask = bitmask;
402        }
403    
404        /**
405         * Gets the name of the XML element.
406         *
407         * @return XML element name
408         */
409        @Override
410        public String getXmlElementName() {
411          return XmlElementNames.Excludes;
412        }
413    
414        /**
415         * Tries to read element from XML.
416         *
417         * @param reader the reader
418         * @return true if element was read
419         * @throws Exception the exception
420         */
421        @Override
422        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
423            throws Exception {
424          boolean result = super.tryReadElementFromXml(reader);
425    
426          if (!result) {
427            if (reader.getLocalName().equals(XmlElementNames.Bitmask)) {
428              // EWS always returns the Bitmask value in hexadecimal
429              this.bitmask = Integer.parseInt(reader
430                  .readAttributeValue(XmlAttributeNames.Value));
431            }
432          }
433    
434          return result;
435        }
436    
437        /**
438         * Writes the elements to XML.
439         *
440         * @param writer the writer
441         * @throws javax.xml.stream.XMLStreamException , ServiceXmlSerializationException
442         * @throws ServiceXmlSerializationException    the service xml serialization exception
443         */
444        @Override
445        public void writeElementsToXml(EwsServiceXmlWriter writer)
446            throws XMLStreamException, ServiceXmlSerializationException {
447          super.writeElementsToXml(writer);
448    
449          writer.writeStartElement(XmlNamespace.Types,
450              XmlElementNames.Bitmask);
451          writer.writeAttributeValue(XmlAttributeNames.Value, this.bitmask);
452          writer.writeEndElement(); // Bitmask
453        }
454    
455        /**
456         * Gets the bitmask to compare the property with.
457         *
458         * @return bitmask
459         */
460        public int getBitmask() {
461          return bitmask;
462        }
463    
464        /**
465         * Sets the bitmask to compare the property with.
466         *
467         * @param bitmask the new bitmask
468         */
469        public void setBitmask(int bitmask) {
470          this.bitmask = bitmask;
471        }
472    
473      }
474    
475    
476      /**
477       * Represents a search filter checking if a field is set. Applications can
478       * use ExistsFilter to define conditions such as "Field IS SET".
479       */
480      public static final class Exists extends PropertyBasedFilter {
481    
482        /**
483         * Initializes a new instance of the class.
484         */
485        public Exists() {
486          super();
487        }
488    
489        /**
490         * Initializes a new instance of the class.
491         *
492         * @param propertyDefinition the property definition
493         */
494        public Exists(PropertyDefinitionBase propertyDefinition) {
495          super(propertyDefinition);
496        }
497    
498        /**
499         * Gets the name of the XML element.
500         *
501         * @return the xml element name
502         */
503        @Override
504        protected String getXmlElementName() {
505          return XmlElementNames.Exists;
506        }
507      }
508    
509    
510      /**
511       * Represents a search filter that checks if a property is equal to a given
512       * value or other property.
513       */
514      public static class IsEqualTo extends RelationalFilter {
515    
516        /**
517         * Initializes a new instance of the class.
518         */
519        public IsEqualTo() {
520          super();
521        }
522    
523        /**
524         * Initializes a new instance of the class.
525         *
526         * @param propertyDefinition      The definition of the property that is being compared.
527         * @param otherPropertyDefinition The definition of the property to compare with.
528         */
529        public IsEqualTo(PropertyDefinitionBase propertyDefinition,
530            PropertyDefinitionBase otherPropertyDefinition) {
531          super(propertyDefinition, otherPropertyDefinition);
532        }
533    
534        /**
535         * Initializes a new instance of the class.
536         *
537         * @param propertyDefinition The definition of the property that is being compared.
538         * @param value              The value of the property to compare with.
539         */
540        public IsEqualTo(PropertyDefinitionBase propertyDefinition,
541            Object value) {
542          super(propertyDefinition, value);
543        }
544    
545        /**
546         * Gets the name of the XML element.
547         *
548         * @return the xml element name
549         */
550        @Override
551        protected String getXmlElementName() {
552          return XmlElementNames.IsEqualTo;
553        }
554    
555      }
556    
557    
558      /**
559       * Represents a search filter that checks if a property is greater than a
560       * given value or other property.
561       */
562      public static class IsGreaterThan extends RelationalFilter {
563    
564        /**
565         * Initializes a new instance of the class.
566         */
567        public IsGreaterThan() {
568          super();
569        }
570    
571        /**
572         * Initializes a new instance of the class.
573         *
574         * @param propertyDefinition      The definition of the property that is being compared.
575         * @param otherPropertyDefinition The definition of the property to compare with.
576         */
577        public IsGreaterThan(PropertyDefinitionBase propertyDefinition,
578            PropertyDefinitionBase otherPropertyDefinition) {
579          super(propertyDefinition, otherPropertyDefinition);
580        }
581    
582        /**
583         * Initializes a new instance of the class.
584         *
585         * @param propertyDefinition The definition of the property that is being compared.
586         * @param value              The value of the property to compare with.
587         */
588        public IsGreaterThan(PropertyDefinitionBase propertyDefinition,
589            Object value) {
590          super(propertyDefinition, value);
591        }
592    
593        /**
594         * Gets the name of the XML element.
595         *
596         * @return XML element name.
597         */
598        @Override
599        protected String getXmlElementName() {
600          return XmlElementNames.IsGreaterThan;
601        }
602      }
603    
604    
605      /**
606       * Represents a search filter that checks if a property is greater than or
607       * equal to a given value or other property.
608       */
609      public static class IsGreaterThanOrEqualTo extends RelationalFilter {
610    
611        /**
612         * Initializes a new instance of the class.
613         */
614        public IsGreaterThanOrEqualTo() {
615          super();
616        }
617    
618        /**
619         * Initializes a new instance of the class.
620         *
621         * @param propertyDefinition      The definition of the property that is being compared.
622         * @param otherPropertyDefinition The definition of the property to compare with.
623         */
624        public IsGreaterThanOrEqualTo(
625            PropertyDefinitionBase propertyDefinition,
626            PropertyDefinitionBase otherPropertyDefinition) {
627          super(propertyDefinition, otherPropertyDefinition);
628        }
629    
630        /**
631         * Initializes a new instance of the class.
632         *
633         * @param propertyDefinition The definition of the property that is being compared.
634         * @param value              The value of the property to compare with.
635         */
636        public IsGreaterThanOrEqualTo(
637            PropertyDefinitionBase propertyDefinition, Object value) {
638          super(propertyDefinition, value);
639        }
640    
641        /**
642         * Gets the name of the XML element. XML element name.
643         *
644         * @return the xml element name
645         */
646        @Override
647        protected String getXmlElementName() {
648          return XmlElementNames.IsGreaterThanOrEqualTo;
649        }
650    
651      }
652    
653    
654      /**
655       * Represents a search filter that checks if a property is less than a given
656       * value or other property.
657       */
658      public static class IsLessThan extends RelationalFilter {
659    
660        /**
661         * Initializes a new instance of the class.
662         */
663        public IsLessThan() {
664          super();
665        }
666    
667        /**
668         * Initializes a new instance of the class.
669         *
670         * @param propertyDefinition      The definition of the property that is being compared.
671         * @param otherPropertyDefinition The definition of the property to compare with.
672         */
673        public IsLessThan(PropertyDefinitionBase propertyDefinition,
674            PropertyDefinitionBase otherPropertyDefinition) {
675          super(propertyDefinition, otherPropertyDefinition);
676        }
677    
678        /**
679         * Initializes a new instance of the class.
680         *
681         * @param propertyDefinition The definition of the property that is being compared.
682         * @param value              The value of the property to compare with.
683         */
684        public IsLessThan(PropertyDefinitionBase propertyDefinition,
685            Object value) {
686          super(propertyDefinition, value);
687        }
688    
689        /**
690         * Gets the name of the XML element. XML element name.
691         *
692         * @return the xml element name
693         */
694        @Override
695        protected String getXmlElementName() {
696          return XmlElementNames.IsLessThan;
697        }
698    
699      }
700    
701    
702      /**
703       * Represents a search filter that checks if a property is less than or
704       * equal to a given value or other property.
705       */
706      public static class IsLessThanOrEqualTo extends RelationalFilter {
707    
708        /**
709         * Initializes a new instance of the class.
710         */
711        public IsLessThanOrEqualTo() {
712          super();
713        }
714    
715        /**
716         * Initializes a new instance of the class.
717         *
718         * @param propertyDefinition      The definition of the property that is being compared.
719         * @param otherPropertyDefinition The definition of the property to compare with.
720         */
721        public IsLessThanOrEqualTo(PropertyDefinitionBase propertyDefinition,
722            PropertyDefinitionBase otherPropertyDefinition) {
723          super(propertyDefinition, otherPropertyDefinition);
724        }
725    
726        /**
727         * Initializes a new instance of the class.
728         *
729         * @param propertyDefinition The definition of the property that is being compared.
730         * @param value              The value of the property to compare with.
731         */
732        public IsLessThanOrEqualTo(PropertyDefinitionBase propertyDefinition,
733            Object value) {
734          super(propertyDefinition, value);
735        }
736    
737        /**
738         * Gets the name of the XML element. XML element name.
739         *
740         * @return the xml element name
741         */
742        @Override
743        protected String getXmlElementName() {
744          return XmlElementNames.IsLessThanOrEqualTo;
745        }
746    
747      }
748    
749    
750      /**
751       * Represents a search filter that checks if a property is not equal to a
752       * given value or other property.
753       */
754      public static class IsNotEqualTo extends RelationalFilter {
755    
756        /**
757         * Initializes a new instance of the class.
758         */
759        public IsNotEqualTo() {
760          super();
761        }
762    
763        /**
764         * Initializes a new instance of the class.
765         *
766         * @param propertyDefinition      The definition of the property that is being compared.
767         * @param otherPropertyDefinition The definition of the property to compare with.
768         */
769        public IsNotEqualTo(PropertyDefinitionBase propertyDefinition,
770            PropertyDefinitionBase otherPropertyDefinition) {
771          super(propertyDefinition, otherPropertyDefinition);
772        }
773    
774        /**
775         * Initializes a new instance of the class.
776         *
777         * @param propertyDefinition The definition of the property that is being compared.
778         * @param value              The value of the property to compare with.
779         */
780        public IsNotEqualTo(PropertyDefinitionBase propertyDefinition,
781            Object value) {
782          super(propertyDefinition, value);
783        }
784    
785        /**
786         * Gets the name of the XML element.
787         *
788         * @return XML element name.
789         */
790        @Override
791        protected String getXmlElementName() {
792          return XmlElementNames.IsNotEqualTo;
793        }
794    
795      }
796    
797    
798      /**
799       * Represents a search filter that negates another. Applications can use
800       * NotFilter to define conditions such as "NOT(other filter)".
801       */
802      public static class Not extends SearchFilter implements IComplexPropertyChangedDelegate {
803    
804        /**
805         * The search filter.
806         */
807        private SearchFilter searchFilter;
808    
809        /**
810         * Initializes a new instance of the class.
811         */
812        public Not() {
813          super();
814        }
815    
816        /**
817         * Initializes a new instance of the class.
818         *
819         * @param searchFilter the search filter
820         */
821        public Not(SearchFilter searchFilter) {
822          super();
823          this.searchFilter = searchFilter;
824        }
825    
826        /**
827         * Search filter changed.
828         *
829         * @param complexProperty the complex property
830         */
831        private void searchFilterChanged(ComplexProperty complexProperty) {
832          this.changed();
833        }
834    
835        /**
836         * validates the instance.
837         *
838         * @throws ServiceValidationException the service validation exception
839         */
840        @Override
841        protected void internalValidate() throws ServiceValidationException {
842          if (this.searchFilter == null) {
843            throw new ServiceValidationException("The SearchFilter property must be set.");
844          }
845        }
846    
847        /**
848         * Gets the name of the XML element.
849         *
850         * @return the xml element name
851         */
852        @Override
853        protected String getXmlElementName() {
854          return XmlElementNames.Not;
855        }
856    
857        /**
858         * Tries to read element from XML.
859         *
860         * @param reader the reader
861         * @return true if the element was read
862         * @throws Exception the exception
863         */
864        @Override
865        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
866            throws Exception {
867          this.searchFilter = SearchFilter.loadFromXml(reader);
868          return true;
869        }
870    
871        /**
872         * Writes the elements to XML.
873         *
874         * @param writer the writer
875         * @throws Exception the exception
876         */
877        @Override
878        public void writeElementsToXml(EwsServiceXmlWriter writer)
879            throws Exception {
880          this.searchFilter.writeToXml(writer);
881        }
882    
883        /**
884         * Gets  the search filter to negate. Available search filter
885         * classes include SearchFilter.IsEqualTo,
886         * SearchFilter.ContainsSubstring and
887         * SearchFilter.SearchFilterCollection.
888         *
889         * @return SearchFilter
890         */
891        public SearchFilter getSearchFilter() {
892          return searchFilter;
893        }
894    
895        /**
896         * Sets the search filter to negate. Available search filter classes
897         * include SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
898         * SearchFilter.SearchFilterCollection.
899         *
900         * @param searchFilter the new search filter
901         */
902        public void setSearchFilter(SearchFilter searchFilter) {
903          if (this.searchFilter != null) {
904            this.searchFilter.removeChangeEvent(this);
905          }
906    
907          if (this.canSetFieldValue(this.searchFilter, searchFilter)) {
908            this.searchFilter = searchFilter;
909            this.changed();
910    
911          }
912    
913          if (this.searchFilter != null) {
914            this.searchFilter.addOnChangeEvent(this);
915          }
916        }
917    
918        /*
919         * (non-Javadoc)
920         *
921         * @see
922         * microsoft.exchange.webservices.
923         * ComplexPropertyChangedDelegateInterface#
924         * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty
925         * )
926         */
927        @Override
928        public void complexPropertyChanged(ComplexProperty complexProperty) {
929          searchFilterChanged(complexProperty);
930    
931        }
932      }
933    
934    
935      /**
936       * Represents a search filter where an item or folder property is involved.
937       */
938      @EditorBrowsable(state = EditorBrowsableState.Never)
939      public static abstract class PropertyBasedFilter extends SearchFilter {
940    
941        /**
942         * The property definition.
943         */
944        private PropertyDefinitionBase propertyDefinition;
945    
946        /**
947         * Initializes a new instance of the class.
948         */
949        PropertyBasedFilter() {
950          super();
951        }
952    
953        /**
954         * Initializes a new instance of the class.
955         *
956         * @param propertyDefinition the property definition
957         */
958        PropertyBasedFilter(PropertyDefinitionBase propertyDefinition) {
959          super();
960          this.propertyDefinition = propertyDefinition;
961        }
962    
963        /**
964         * validate instance.
965         *
966         * @throws ServiceValidationException the service validation exception
967         */
968        @Override
969        protected void internalValidate() throws ServiceValidationException {
970          if (this.propertyDefinition == null) {
971            throw new ServiceValidationException("The PropertyDefinition property must be set.");
972          }
973        }
974    
975        /**
976         * Tries to read element from XML.
977         *
978         * @param reader the reader
979         * @return true if element was read
980         * @throws Exception the exception
981         */
982        @Override
983        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
984            throws Exception {
985          OutParam<PropertyDefinitionBase> outParam =
986              new OutParam<PropertyDefinitionBase>();
987          outParam.setParam(this.propertyDefinition);
988    
989          return PropertyDefinitionBase.tryLoadFromXml(reader, outParam);
990        }
991    
992        /**
993         * Writes the elements to XML.
994         *
995         * @param writer the writer
996         * @throws XMLStreamException the XML stream exception
997         * @throws ServiceXmlSerializationException the service xml serialization exception
998         */
999        @Override
1000        public void writeElementsToXml(EwsServiceXmlWriter writer)
1001            throws XMLStreamException, ServiceXmlSerializationException {
1002          this.propertyDefinition.writeToXml(writer);
1003        }
1004    
1005        /**
1006         * Gets the definition of the property that is involved in the search
1007         * filter.
1008         *
1009         * @return propertyDefinition
1010         */
1011        public PropertyDefinitionBase getPropertyDefinition() {
1012          return this.propertyDefinition;
1013        }
1014    
1015        /**
1016         * Sets the definition of the property that is involved in the search
1017         * filter.
1018         *
1019         * @param propertyDefinition the new property definition
1020         */
1021        public void setPropertyDefinition(
1022            PropertyDefinitionBase propertyDefinition) {
1023          this.propertyDefinition = propertyDefinition;
1024        }
1025      }
1026    
1027    
1028      /**
1029       * Represents the base class for relational filter (for example, IsEqualTo,
1030       * IsGreaterThan or IsLessThanOrEqualTo).
1031       */
1032      @EditorBrowsable(state = EditorBrowsableState.Never)
1033      public abstract static class RelationalFilter extends PropertyBasedFilter {
1034    
1035        /**
1036         * The other property definition.
1037         */
1038        private PropertyDefinitionBase otherPropertyDefinition;
1039    
1040        /**
1041         * The value.
1042         */
1043        private Object value;
1044    
1045        /**
1046         * Initializes a new instance of the class.
1047         */
1048        RelationalFilter() {
1049          super();
1050        }
1051    
1052        /**
1053         * Initializes a new instance of the class.
1054         *
1055         * @param propertyDefinition      The definition of the property that is being compared.
1056         * @param otherPropertyDefinition The definition of the property to compare with
1057         */
1058        RelationalFilter(PropertyDefinitionBase propertyDefinition,
1059            PropertyDefinitionBase otherPropertyDefinition) {
1060          super(propertyDefinition);
1061          this.otherPropertyDefinition = otherPropertyDefinition;
1062        }
1063    
1064        /**
1065         * Initializes a new instance of the class.
1066         *
1067         * @param propertyDefinition The definition of the property that is being compared.
1068         * @param value              The value to compare with.
1069         */
1070        RelationalFilter(PropertyDefinitionBase propertyDefinition,
1071            Object value) {
1072          super(propertyDefinition);
1073          this.value = value;
1074        }
1075    
1076        /**
1077         * validates the instance.
1078         *
1079         * @throws ServiceValidationException the service validation exception
1080         */
1081        @Override
1082        protected void internalValidate() throws ServiceValidationException {
1083          super.internalValidate();
1084    
1085          if (this.otherPropertyDefinition == null && this.value == null) {
1086            throw new ServiceValidationException(
1087                "Either the OtherPropertyDefinition or the Value property must be set.");
1088          }
1089        }
1090    
1091        /**
1092         * Tries to read element from XML.
1093         *
1094         * @param reader the reader
1095         * @return true if element was read
1096         * @throws Exception the exception
1097         */
1098        @Override
1099        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
1100            throws Exception {
1101          boolean result = super.tryReadElementFromXml(reader);
1102    
1103          if (!result) {
1104            if (reader.getLocalName().equals(
1105                XmlElementNames.FieldURIOrConstant)) {
1106              try {
1107                reader.read();
1108                reader.ensureCurrentNodeIsStartElement();
1109              } catch (ServiceXmlDeserializationException e) {
1110                LOG.error(e);
1111              } catch (XMLStreamException e) {
1112                LOG.error(e);
1113              }
1114    
1115              if (reader.isStartElement(XmlNamespace.Types,
1116                  XmlElementNames.Constant)) {
1117                this.value = reader
1118                    .readAttributeValue(XmlAttributeNames.Value);
1119                result = true;
1120              } else {
1121                OutParam<PropertyDefinitionBase> outParam =
1122                    new OutParam<PropertyDefinitionBase>();
1123                outParam.setParam(this.otherPropertyDefinition);
1124    
1125                result = PropertyDefinitionBase.tryLoadFromXml(reader,
1126                    outParam);
1127              }
1128            }
1129          }
1130    
1131          return result;
1132        }
1133    
1134        /**
1135         * Writes the elements to XML.
1136         *
1137         * @param writer the writer
1138         * @throws javax.xml.stream.XMLStreamException , ServiceXmlSerializationException
1139         * @throws ServiceXmlSerializationException    the service xml serialization exception
1140         */
1141        @Override
1142        public void writeElementsToXml(EwsServiceXmlWriter writer)
1143            throws XMLStreamException, ServiceXmlSerializationException {
1144          super.writeElementsToXml(writer);
1145    
1146          writer.writeStartElement(XmlNamespace.Types,
1147              XmlElementNames.FieldURIOrConstant);
1148    
1149          if (this.value != null) {
1150            writer.writeStartElement(XmlNamespace.Types,
1151                XmlElementNames.Constant);
1152            writer.writeAttributeValue(XmlAttributeNames.Value,
1153                true /* alwaysWriteEmptyString */, this.value);
1154            writer.writeEndElement(); // Constant
1155          } else {
1156            this.otherPropertyDefinition.writeToXml(writer);
1157          }
1158    
1159          writer.writeEndElement(); // FieldURIOrConstant
1160        }
1161    
1162        /**
1163         * Gets the definition of the property to compare with.
1164         *
1165         * @return otherPropertyDefinition
1166         */
1167        public PropertyDefinitionBase getOtherPropertyDefinition() {
1168          return this.otherPropertyDefinition;
1169        }
1170    
1171        /**
1172         * Sets the definition of the property to compare with.
1173         *
1174         * @param OtherPropertyDefinition the new other property definition
1175         */
1176        public void setOtherPropertyDefinition(
1177            PropertyDefinitionBase OtherPropertyDefinition) {
1178          this.otherPropertyDefinition = OtherPropertyDefinition;
1179          this.value = null;
1180        }
1181    
1182        /**
1183         * Gets the value of the property to compare with.
1184         *
1185         * @return the value
1186         */
1187        public Object getValue() {
1188          return value;
1189        }
1190    
1191        /**
1192         * Sets the value of the property to compare with.
1193         *
1194         * @param value the new value
1195         */
1196        public void setValue(Object value) {
1197          this.value = value;
1198          this.otherPropertyDefinition = null;
1199        }
1200    
1201        /**
1202         * gets Xml Element name.
1203         *
1204         * @return the xml element name
1205         */
1206        @Override
1207        protected String getXmlElementName() {
1208          return null;
1209        }
1210      }
1211    
1212    
1213      /**
1214       * Represents a collection of search filter linked by a logical operator.
1215       * Applications can use SearchFilterCollection to define complex search
1216       * filter such as "Condition1 AND Condition2".
1217       */
1218      public static class SearchFilterCollection extends SearchFilter implements
1219          Iterable<SearchFilter>, IComplexPropertyChangedDelegate {
1220    
1221        /**
1222         * The logical operator.
1223         */
1224        private LogicalOperator logicalOperator = LogicalOperator.And;
1225    
1226        /**
1227         * The search filter.
1228         */
1229        private ArrayList<SearchFilter> searchFilters =
1230            new ArrayList<SearchFilter>();
1231    
1232        /**
1233         * Initializes a new instance of the class.
1234         */
1235        public SearchFilterCollection() {
1236          super();
1237        }
1238    
1239        /**
1240         * Initializes a new instance of the class.
1241         *
1242         * @param logicalOperator The logical operator used to initialize the collection.
1243         */
1244        public SearchFilterCollection(LogicalOperator logicalOperator) {
1245          this.logicalOperator = logicalOperator;
1246        }
1247    
1248        /**
1249         * Initializes a new instance of the class.
1250         *
1251         * @param logicalOperator The logical operator used to initialize the collection.
1252         * @param searchFilters   The search filter to add to the collection.
1253         */
1254        public SearchFilterCollection(LogicalOperator logicalOperator,
1255            SearchFilter... searchFilters) {
1256          this(logicalOperator);
1257          for (SearchFilter search : searchFilters) {
1258            Iterable<SearchFilter> searchFil = java.util.Arrays
1259                .asList(search);
1260            this.addRange(searchFil);
1261          }
1262        }
1263    
1264        /**
1265         * Initializes a new instance of the class.
1266         *
1267         * @param logicalOperator The logical operator used to initialize the collection.
1268         * @param searchFilters   The search filter to add to the collection.
1269         */
1270        public SearchFilterCollection(LogicalOperator logicalOperator,
1271            Iterable<SearchFilter> searchFilters) {
1272          this(logicalOperator);
1273          this.addRange(searchFilters);
1274        }
1275    
1276        /**
1277         * Validate instance.
1278         *
1279         * @throws Exception
1280         */
1281        @Override
1282        protected void internalValidate() throws Exception {
1283          for (int i = 0; i < this.getCount(); i++) {
1284            try {
1285              this.searchFilters.get(i).internalValidate();
1286            } catch (ServiceValidationException e) {
1287              throw new ServiceValidationException(String.format("The search filter at index %d is invalid.", i),
1288                  e);
1289            }
1290          }
1291        }
1292    
1293        /**
1294         * A search filter has changed.
1295         *
1296         * @param complexProperty The complex property
1297         */
1298        private void searchFilterChanged(ComplexProperty complexProperty) {
1299          this.changed();
1300        }
1301    
1302        /**
1303         * Gets the name of the XML element.
1304         *
1305         * @return xml element name
1306         */
1307        @Override
1308        protected String getXmlElementName() {
1309          return this.logicalOperator.toString();
1310        }
1311    
1312        /**
1313         * Tries to read element from XML.
1314         *
1315         * @param reader the reader
1316         * @return true, if successful
1317         * @throws Exception the exception
1318         */
1319        @Override
1320        public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
1321            throws Exception {
1322    
1323          this.add(SearchFilter.loadFromXml(reader));
1324          return true;
1325        }
1326    
1327        /**
1328         * Writes the elements to XML.
1329         *
1330         * @param writer the writer
1331         * @throws Exception the exception
1332         */
1333        @Override
1334        public void writeElementsToXml(EwsServiceXmlWriter writer)
1335            throws Exception {
1336          for (SearchFilter searchFilter : this.searchFilters) {
1337            searchFilter.writeToXml(writer);
1338          }
1339        }
1340    
1341        /**
1342         * Writes to XML.
1343         *
1344         * @param writer the writer
1345         * @throws Exception the exception
1346         */
1347        @Override public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
1348          // If there is only one filter in the collection, which developers
1349          // tend
1350          // to do,
1351          // we need to not emit the collection and instead only emit the one
1352          // filter within
1353          // the collection. This is to work around the fact that EWS does not
1354          // allow filter
1355          // collections that have less than two elements.
1356          if (this.getCount() == 1) {
1357            this.searchFilters.get(0).writeToXml(writer);
1358          } else {
1359            super.writeToXml(writer);
1360          }
1361        }
1362    
1363        /**
1364         * Adds a search filter of any type to the collection.
1365         *
1366         * @param searchFilter >The search filter to add. Available search filter classes
1367         *                     include SearchFilter.IsEqualTo,
1368         *                     SearchFilter.ContainsSubstring and
1369         *                     SearchFilter.SearchFilterCollection.
1370         */
1371        public void add(SearchFilter searchFilter) {
1372          if (searchFilter == null) {
1373            throw new IllegalArgumentException("searchFilter");
1374          }
1375          searchFilter.addOnChangeEvent(this);
1376          this.searchFilters.add(searchFilter);
1377          this.changed();
1378        }
1379    
1380        /**
1381         * Adds multiple search filter to the collection.
1382         *
1383         * @param searchFilters The search filter to add. Available search filter classes
1384         *                      include SearchFilter.IsEqualTo,
1385         *                      SearchFilter.ContainsSubstring and
1386         *                      SearchFilter.SearchFilterCollection
1387         */
1388        public void addRange(Iterable<SearchFilter> searchFilters) {
1389          if (searchFilters == null) {
1390            throw new IllegalArgumentException("searchFilters");
1391          }
1392    
1393          for (SearchFilter searchFilter : searchFilters) {
1394            searchFilter.addOnChangeEvent(this);
1395            this.searchFilters.add(searchFilter);
1396          }
1397          this.changed();
1398        }
1399    
1400        /**
1401         * Clears the collection.
1402         */
1403        public void clear() {
1404          if (this.getCount() > 0) {
1405            for (SearchFilter searchFilter : this.searchFilters) {
1406              searchFilter.removeChangeEvent(this);
1407            }
1408            this.searchFilters.clear();
1409            this.changed();
1410          }
1411        }
1412    
1413        /**
1414         * Determines whether a specific search filter is in the collection.
1415         *
1416         * @param searchFilter The search filter to locate in the collection.
1417         * @return True is the search filter was found in the collection, false
1418         * otherwise.
1419         */
1420        public boolean contains(SearchFilter searchFilter) {
1421          return this.searchFilters.contains(searchFilter);
1422        }
1423    
1424        /**
1425         * Removes a search filter from the collection.
1426         *
1427         * @param searchFilter The search filter to remove
1428         */
1429        public void remove(SearchFilter searchFilter) {
1430          if (searchFilter == null) {
1431            throw new IllegalArgumentException("searchFilter");
1432          }
1433    
1434          if (this.contains(searchFilter)) {
1435            searchFilter.removeChangeEvent(this);
1436            this.searchFilters.remove(searchFilter);
1437            this.changed();
1438          }
1439        }
1440    
1441        /**
1442         * Removes the search filter at the specified index from the collection.
1443         *
1444         * @param index The zero-based index of the search filter to remove.
1445         */
1446        public void removeAt(int index) {
1447          if (index < 0 || index >= this.getCount()) {
1448            throw new IllegalArgumentException(
1449                String.format("index %d is out of range [0..%d[.", index, this.getCount()));
1450          }
1451    
1452          this.searchFilters.get(index).removeChangeEvent(this);
1453          this.searchFilters.remove(index);
1454          this.changed();
1455        }
1456    
1457        /**
1458         * Gets the total number of search filter in the collection.
1459         *
1460         * @return the count
1461         */
1462        public int getCount() {
1463    
1464          return this.searchFilters.size();
1465        }
1466    
1467        /**
1468         * Gets the search filter at the specified index.
1469         *
1470         * @param index the index
1471         * @return The search filter at the specified index.
1472         */
1473        public SearchFilter getSearchFilter(int index) {
1474          if (index < 0 || index >= this.getCount()) {
1475            throw new IllegalArgumentException(
1476                String.format("index %d is out of range [0..%d[.", index, this.getCount())
1477            );
1478          }
1479          return this.searchFilters.get(index);
1480        }
1481    
1482        /**
1483         * Sets the search filter at the specified index.
1484         *
1485         * @param index        the index
1486         * @param searchFilter the search filter
1487         */
1488        public void setSearchFilter(int index, SearchFilter searchFilter) {
1489          if (index < 0 || index >= this.getCount()) {
1490            throw new IllegalArgumentException(
1491                String.format("index %d is out of range [0..%d[.", index, this.getCount())
1492            );
1493          }
1494          this.searchFilters.add(index, searchFilter);
1495        }
1496    
1497        /**
1498         * Gets the logical operator that links the serach filter in this
1499         * collection.
1500         *
1501         * @return LogicalOperator
1502         */
1503        public LogicalOperator getLogicalOperator() {
1504          return logicalOperator;
1505        }
1506    
1507        /**
1508         * Sets the logical operator that links the serach filter in this
1509         * collection.
1510         *
1511         * @param logicalOperator the new logical operator
1512         */
1513        public void setLogicalOperator(LogicalOperator logicalOperator) {
1514          this.logicalOperator = logicalOperator;
1515        }
1516    
1517        /*
1518         * (non-Javadoc)
1519         *
1520         * @see
1521         * microsoft.exchange.webservices.
1522         * ComplexPropertyChangedDelegateInterface#
1523         * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty
1524         * )
1525         */
1526        @Override
1527        public void complexPropertyChanged(ComplexProperty complexProperty) {
1528          searchFilterChanged(complexProperty);
1529        }
1530    
1531        /*
1532         * (non-Javadoc)
1533         *
1534         * @see java.lang.Iterable#iterator()
1535         */
1536        @Override
1537        public Iterator<SearchFilter> iterator() {
1538          return this.searchFilters.iterator();
1539        }
1540    
1541      }
1542    }