001/*
002 * Copyright 2015-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 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
057 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
058 *   for proprietary functionality or for external specifications that are not
059 *   considered stable or mature enough to be guaranteed to work in an
060 *   interoperable way with 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<>(
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<>(
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(new ArrayList<>(orFilters));
263    }
264  }
265
266
267
268  /**
269   * Indicates whether this filter should be treated as an exclusive OR, in
270   * which it will only match a JSON object if exactly one of the embedded
271   * filters matches that object.
272   *
273   * @return  {@code true} if this filter should be treated as an exclusive OR
274   *          and will only match a JSON object if exactly one of the embedded
275   *          filters matches that object, or {@code false} if this filter will
276   *          be non-exclusive and will match a JSON object as long as at least
277   *          one of the embedded filters matches that object.
278   */
279  public boolean exclusive()
280  {
281    return exclusive;
282  }
283
284
285
286  /**
287   * Specifies whether this filter should be treated as an exclusive OR, in
288   * which it will only match a JSON object if exactly one of the embedded
289   * filters matches that object.
290   *
291   * @param  exclusive  Indicates whether this filter should be treated as an
292   *                    exclusive OR.
293   */
294  public void setExclusive(final boolean exclusive)
295  {
296    this.exclusive = exclusive;
297  }
298
299
300
301  /**
302   * {@inheritDoc}
303   */
304  @Override()
305  public String getFilterType()
306  {
307    return FILTER_TYPE;
308  }
309
310
311
312  /**
313   * {@inheritDoc}
314   */
315  @Override()
316  protected Set<String> getRequiredFieldNames()
317  {
318    return REQUIRED_FIELD_NAMES;
319  }
320
321
322
323  /**
324   * {@inheritDoc}
325   */
326  @Override()
327  protected Set<String> getOptionalFieldNames()
328  {
329    return OPTIONAL_FIELD_NAMES;
330  }
331
332
333
334  /**
335   * {@inheritDoc}
336   */
337  @Override()
338  public boolean matchesJSONObject(final JSONObject o)
339  {
340    boolean matchFound = false;
341    for (final JSONObjectFilter f : orFilters)
342    {
343      if (f.matchesJSONObject(o))
344      {
345        if (exclusive)
346        {
347          if (matchFound)
348          {
349            return false;
350          }
351          else
352          {
353            matchFound = true;
354          }
355        }
356        else
357        {
358          return true;
359        }
360      }
361    }
362
363    return matchFound;
364  }
365
366
367
368  /**
369   * {@inheritDoc}
370   */
371  @Override()
372  public JSONObject toJSONObject()
373  {
374    final LinkedHashMap<String,JSONValue> fields =
375         new LinkedHashMap<>(StaticUtils.computeMapCapacity(3));
376
377    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
378
379    final ArrayList<JSONValue> filterValues = new ArrayList<>(orFilters.size());
380    for (final JSONObjectFilter f : orFilters)
381    {
382      filterValues.add(f.toJSONObject());
383    }
384    fields.put(FIELD_OR_FILTERS, new JSONArray(filterValues));
385
386    if (exclusive)
387    {
388      fields.put(FIELD_EXCLUSIVE, JSONBoolean.TRUE);
389    }
390
391    return new JSONObject(fields);
392  }
393
394
395
396  /**
397   * {@inheritDoc}
398   */
399  @Override()
400  protected ORJSONObjectFilter decodeFilter(final JSONObject filterObject)
401            throws JSONException
402  {
403    final ORJSONObjectFilter orFilter =
404         new ORJSONObjectFilter(getFilters(filterObject, FIELD_OR_FILTERS));
405    orFilter.exclusive = getBoolean(filterObject, FIELD_EXCLUSIVE, false);
406    return orFilter;
407  }
408}