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