001    /*
002     * Copyright 2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.unboundidds.jsonfilter;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashSet;
029    import java.util.LinkedHashMap;
030    import java.util.List;
031    import java.util.Set;
032    
033    import com.unboundid.util.Mutable;
034    import com.unboundid.util.StaticUtils;
035    import com.unboundid.util.ThreadSafety;
036    import com.unboundid.util.ThreadSafetyLevel;
037    import com.unboundid.util.json.JSONArray;
038    import com.unboundid.util.json.JSONBoolean;
039    import com.unboundid.util.json.JSONException;
040    import com.unboundid.util.json.JSONObject;
041    import com.unboundid.util.json.JSONString;
042    import com.unboundid.util.json.JSONValue;
043    
044    
045    
046    /**
047     * <BLOCKQUOTE>
048     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
049     *   LDAP SDK for Java.  It is not available for use in applications that
050     *   include only the Standard Edition of the LDAP SDK, and is not supported for
051     *   use in conjunction with non-UnboundID products.
052     * </BLOCKQUOTE>
053     * This class provides an implementation of a JSON object filter that can
054     * perform a logical OR across the result obtained from a number of filters.
055     * The OR filter will match an object only if at least one (and optionally,
056     * exactly one) of the filters contained in it matches that object.  An OR
057     * filter with an empty set of embedded filters will never match any object.
058     * <BR><BR>
059     * The fields that are required to be included in an "OR" filter are:
060     * <UL>
061     *   <LI>
062     *     {@code orFilters} -- An array of JSON objects, each of which is a valid
063     *     JSON object filter.  At least one of these filters must match a JSON
064     *     object in order for the OR filter to match.  If this is an empty array,
065     *     then the filter will not match any object.
066     *   </LI>
067     * </UL>
068     * The fields that may optionally be included in an "OR" filter are:
069     * <UL>
070     *   <LI>
071     *     {@code exclusive} -- Indicates whether this should be treated as an
072     *     exclusive OR.  If this is present, then it must have a Boolean value of
073     *     either {@code true} (to indicate that this OR filter will only match a
074     *     JSON object if exactly one of the embedded filters matches that object),
075     *     or {@code false} (to indicate that it is a non-exclusive OR and will
076     *     match a JSON object as long as at least one of the filters matches that
077     *     object).  If this is not specified, then a non-exclusive OR will be
078     *     performed.
079     *   </LI>
080     * </UL>
081     * <H2>Examples</H2>
082     * The following is an example of an OR filter that will never match any JSON
083     * object:
084     * <PRE>
085     *   { "filterType" : "or",
086     *     "orFilters" : [ ] }
087     * </PRE>
088     * The above filter can be created with the code:
089     * <PRE>
090     *   ORJSONObjectFilter filter = new ORJSONObjectFilter();
091     * </PRE>
092     * <BR><BR>
093     * The following is an example of an OR filter that will match any JSON object
094     * that contains either a top-level field named "homePhone" or a top-level
095     * field named "workPhone":
096     * <PRE>
097     *   { "filterType" : "or",
098     *     "orFilters" : [
099     *       { "filterType" : "containsField",
100     *          "field" : "homePhone" },
101     *       { "filterType" : "containsField",
102     *          "field" : "workPhone" } ] }
103     * </PRE>
104     * The above filter can be created with the code:
105     * <PRE>
106     *   ORJSONObjectFilter filter = new ORJSONObjectFilter(
107     *        new ContainsFieldJSONObjectFilter("homePhone"),
108     *        new EqualsJSONObjectFilter("workPhone"));
109     * </PRE>
110     */
111    @Mutable()
112    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
113    public final class ORJSONObjectFilter
114           extends JSONObjectFilter
115    {
116      /**
117       * The value that should be used for the filterType element of the JSON object
118       * that represents an "OR" filter.
119       */
120      public static final String FILTER_TYPE = "or";
121    
122    
123    
124      /**
125       * The name of the JSON field that is used to specify the set of filters to
126       * include in this OR filter.
127       */
128      public static final String FIELD_OR_FILTERS = "orFilters";
129    
130    
131    
132      /**
133       * The name of the JSON field that is used to indicate whether this should be
134       * an exclusive OR.
135       */
136      public static final String FIELD_EXCLUSIVE = "exclusive";
137    
138    
139    
140      /**
141       * The pre-allocated set of required field names.
142       */
143      private static final Set<String> REQUIRED_FIELD_NAMES =
144           Collections.unmodifiableSet(new HashSet<String>(
145                Collections.singletonList(FIELD_OR_FILTERS)));
146    
147    
148    
149      /**
150       * The pre-allocated set of optional field names.
151       */
152      private static final Set<String> OPTIONAL_FIELD_NAMES =
153           Collections.unmodifiableSet(new HashSet<String>(
154                Collections.singletonList(FIELD_EXCLUSIVE)));
155    
156    
157    
158      /**
159       * The serial version UID for this serializable class.
160       */
161      private static final long serialVersionUID = -7821418213623654386L;
162    
163    
164    
165      // Indicates whether to process this filter as an exclusive OR.
166      private volatile boolean exclusive;
167    
168      // The set of embedded filters for this OR filter.
169      private volatile List<JSONObjectFilter> orFilters;
170    
171    
172    
173      /**
174       * Creates a new instance of this filter type with the provided information.
175       *
176       * @param  orFilters  The set of filters for this OR filter.  At least one
177       *                    of these filters must match a JSON object in order for
178       *                    this OR filter to match that object.  If this is
179       *                    {@code null} or empty, then this OR filter will never
180       *                    match any JSON object.
181       */
182      public ORJSONObjectFilter(final JSONObjectFilter... orFilters)
183      {
184        this(StaticUtils.toList(orFilters));
185      }
186    
187    
188    
189      /**
190       * Creates a new instance of this filter type with the provided information.
191       *
192       * @param  orFilters  The set of filters for this OR filter.  At least one
193       *                    of these filters must match a JSON object in order for
194       *                    this OR filter to match that object.  If this is
195       *                    {@code null} or empty, then this OR filter will never
196       *                    match any JSON object.
197       */
198      public ORJSONObjectFilter(final Collection<JSONObjectFilter> orFilters)
199      {
200        setORFilters(orFilters);
201    
202        exclusive = false;
203      }
204    
205    
206    
207      /**
208       * Retrieves the set of filters for this OR filter.  At least one of these
209       * filters must match a JSON object in order fro this OR filter to match that
210       * object.
211       *
212       * @return  The set of filters for this OR filter.
213       */
214      public List<JSONObjectFilter> getORFilters()
215      {
216        return orFilters;
217      }
218    
219    
220    
221      /**
222       * Specifies the set of filters for this OR filter.  At least one of these
223       * filters must match a JSON object in order for this OR filter to match that
224       * object.
225       *
226       * @param  orFilters  The set of filters for this OR filter.  At least one
227       *                    of these filters must match a JSON object in order for
228       *                    this OR filter to match that object.  If this is
229       *                    {@code null} or empty, then this OR filter will never
230       *                    match any JSON object.
231       */
232      public void setORFilters(final JSONObjectFilter... orFilters)
233      {
234        setORFilters(StaticUtils.toList(orFilters));
235      }
236    
237    
238    
239      /**
240       * Specifies the set of filters for this OR filter.  At least one of these
241       * filters must match a JSON object in order for this OR filter to match that
242       * object.
243       *
244       * @param  orFilters  The set of filters for this OR filter.  At least one
245       *                    of these filters must match a JSON object in order for
246       *                    this OR filter to match that object.  If this is
247       *                    {@code null} or empty, then this OR filter will never
248       *                    match any JSON object.
249       */
250      public void setORFilters(final Collection<JSONObjectFilter> orFilters)
251      {
252        if ((orFilters == null) || orFilters.isEmpty())
253        {
254          this.orFilters = Collections.emptyList();
255        }
256        else
257        {
258          this.orFilters = Collections.unmodifiableList(
259               new ArrayList<JSONObjectFilter>(orFilters));
260        }
261      }
262    
263    
264    
265      /**
266       * Indicates whether this filter should be treated as an exclusive OR, in
267       * which it will only match a JSON object if exactly one of the embedded
268       * filters matches that object.
269       *
270       * @return  {@code true} if this filter should be treated as an exclusive OR
271       *          and will only match a JSON object if exactly one of the embedded
272       *          filters matches that object, or {@code false} if this filter will
273       *          be non-exclusive and will match a JSON object as long as at least
274       *          one of the embedded filters matches that object.
275       */
276      public boolean exclusive()
277      {
278        return exclusive;
279      }
280    
281    
282    
283      /**
284       * Specifies whether this filter should be treated as an exclusive OR, in
285       * which it will only match a JSON object if exactly one of the embedded
286       * filters matches that object.
287       *
288       * @param  exclusive  Indicates whether this filter should be treated as an
289       *                    exclusive OR.
290       */
291      public void setExclusive(final boolean exclusive)
292      {
293        this.exclusive = exclusive;
294      }
295    
296    
297    
298      /**
299       * {@inheritDoc}
300       */
301      @Override()
302      public String getFilterType()
303      {
304        return FILTER_TYPE;
305      }
306    
307    
308    
309      /**
310       * {@inheritDoc}
311       */
312      @Override()
313      protected Set<String> getRequiredFieldNames()
314      {
315        return REQUIRED_FIELD_NAMES;
316      }
317    
318    
319    
320      /**
321       * {@inheritDoc}
322       */
323      @Override()
324      protected Set<String> getOptionalFieldNames()
325      {
326        return OPTIONAL_FIELD_NAMES;
327      }
328    
329    
330    
331      /**
332       * {@inheritDoc}
333       */
334      @Override()
335      public boolean matchesJSONObject(final JSONObject o)
336      {
337        boolean matchFound = false;
338        for (final JSONObjectFilter f : orFilters)
339        {
340          if (f.matchesJSONObject(o))
341          {
342            if (exclusive)
343            {
344              if (matchFound)
345              {
346                return false;
347              }
348              else
349              {
350                matchFound = true;
351              }
352            }
353            else
354            {
355              return true;
356            }
357          }
358        }
359    
360        return matchFound;
361      }
362    
363    
364    
365      /**
366       * {@inheritDoc}
367       */
368      @Override()
369      public JSONObject toJSONObject()
370      {
371        final LinkedHashMap<String,JSONValue> fields =
372             new LinkedHashMap<String,JSONValue>(3);
373    
374        fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
375    
376        final ArrayList<JSONValue> filterValues =
377             new ArrayList<JSONValue>(orFilters.size());
378        for (final JSONObjectFilter f : orFilters)
379        {
380          filterValues.add(f.toJSONObject());
381        }
382        fields.put(FIELD_OR_FILTERS, new JSONArray(filterValues));
383    
384        if (exclusive)
385        {
386          fields.put(FIELD_EXCLUSIVE, JSONBoolean.TRUE);
387        }
388    
389        return new JSONObject(fields);
390      }
391    
392    
393    
394      /**
395       * {@inheritDoc}
396       */
397      @Override()
398      protected ORJSONObjectFilter decodeFilter(final JSONObject filterObject)
399                throws JSONException
400      {
401        final ORJSONObjectFilter orFilter =
402             new ORJSONObjectFilter(getFilters(filterObject, FIELD_OR_FILTERS));
403        orFilter.exclusive = getBoolean(filterObject, FIELD_EXCLUSIVE, false);
404        return orFilter;
405      }
406    }