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.Arrays;
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.Validator;
038import com.unboundid.util.json.JSONArray;
039import com.unboundid.util.json.JSONBoolean;
040import com.unboundid.util.json.JSONException;
041import com.unboundid.util.json.JSONNumber;
042import com.unboundid.util.json.JSONObject;
043import com.unboundid.util.json.JSONString;
044import com.unboundid.util.json.JSONValue;
045
046
047
048/**
049 * This class provides an implementation of a JSON object filter that can be
050 * used to identify JSON objects that have a particular value for a specified
051 * field.
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 "equals" filter are:
064 * <UL>
065 *   <LI>
066 *     {@code fieldName} -- A field path specifier for the JSON field for which
067 *     to make the determination.  This may be either a single string or an
068 *     array of strings as described in the "Targeting Fields in JSON Objects"
069 *     section of the class-level documentation for {@link JSONObjectFilter}.
070 *   </LI>
071 *   <LI>
072 *     {@code value} -- The value to match.  This value may be of any type.  In
073 *     order for a JSON object to match the equals filter, the value of the
074 *     target field must either have the same type value as this value, or the
075 *     value of the target field must be an array containing at least one
076 *     element with the same type and value.  If the provided value is an array,
077 *     then the order, types, and values of the array must match an array
078 *     contained in the target field.  If the provided value is a JSON object,
079 *     then the target field must contain a JSON object with exactly the same
080 *     set of fields and values.
081 *   </LI>
082 * </UL>
083 * The fields that may optionally be included in an "equals" filter are:
084 * <UL>
085 *   <LI>
086 *     {@code caseSensitive} -- Indicates whether string values should be
087 *     treated in a case-sensitive manner.  If present, this field must have a
088 *     Boolean value of either {@code true} or {@code false}.  If it is not
089 *     provided, then a default value of {@code false} will be assumed so that
090 *     strings are treated in a case-insensitive manner.
091 *   </LI>
092 * </UL>
093 * <H2>Examples</H2>
094 * The following is an example of an "equals" filter that will match any JSON
095 * object with a top-level field named "firstName" with a value of "John":
096 * <PRE>
097 *   { "filterType" : "equals",
098 *     "field" : "firstName",
099 *     "value" : "John" }
100 * </PRE>
101 * The above filter can be created with the code:
102 * <PRE>
103 *   EqualsJSONObjectFilter filter =
104 *        new EqualsJSONObjectFilter("firstName", "John");
105 * </PRE>
106 * The following is an example of an "equals" filter that will match a JSON
107 * object with a top-level field named "contact" whose value is a JSON object
108 * (or an array containing one or more JSON objects) with a field named "type"
109 * and a value of "home":
110 * <PRE>
111 *   { "filterType" : "equals",
112 *     "field" : [ "contact", "type" ],
113 *     "value" : "home" }
114 * </PRE>
115 * That filter can be created with the code:
116 * <PRE>
117 *   EqualsJSONObjectFilter filter =
118 *        new EqualsJSONObjectFilter(Arrays.asList("contact", "type"), "Home");
119 * </PRE>
120 */
121@Mutable()
122@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
123public final class EqualsJSONObjectFilter
124       extends JSONObjectFilter
125{
126  /**
127   * The value that should be used for the filterType element of the JSON object
128   * that represents an "equals" filter.
129   */
130  public static final String FILTER_TYPE = "equals";
131
132
133
134  /**
135   * The name of the JSON field that is used to specify the field in the target
136   * JSON object for which to make the determination.
137   */
138  public static final String FIELD_FIELD_PATH = "field";
139
140
141
142  /**
143   * The name of the JSON field that is used to specify the value to use for
144   * the matching.
145   */
146  public static final String FIELD_VALUE = "value";
147
148
149
150  /**
151   * The name of the JSON field that is used to indicate whether string matching
152   * should be case-sensitive.
153   */
154  public static final String FIELD_CASE_SENSITIVE = "caseSensitive";
155
156
157
158  /**
159   * The pre-allocated set of required field names.
160   */
161  private static final Set<String> REQUIRED_FIELD_NAMES =
162       Collections.unmodifiableSet(new HashSet<String>(
163            Arrays.asList(FIELD_FIELD_PATH, FIELD_VALUE)));
164
165
166
167  /**
168   * The pre-allocated set of optional field names.
169   */
170  private static final Set<String> OPTIONAL_FIELD_NAMES =
171       Collections.unmodifiableSet(new HashSet<String>(
172            Collections.singletonList(FIELD_CASE_SENSITIVE)));
173
174
175
176  /**
177   * The serial version UID for this serializable class.
178   */
179  private static final long serialVersionUID = 4622567662624840125L;
180
181
182
183  // Indicates whether string matching should be case-sensitive.
184  private volatile boolean caseSensitive;
185
186  // The expected value for the target field.
187  private volatile JSONValue value;
188
189  // The path name specifier for the target field.
190  private volatile List<String> field;
191
192
193
194  /**
195   * Creates an instance of this filter type that can only be used for decoding
196   * JSON objects as "equals" filters.  It cannot be used as a regular "equals"
197   * filter.
198   */
199  EqualsJSONObjectFilter()
200  {
201    field = null;
202    value = null;
203    caseSensitive = false;
204  }
205
206
207
208  /**
209   * Creates a new instance of this filter type with the provided information.
210   *
211   * @param  field          The path name specifier for the target field.
212   * @param  value          The expected value for the target field.
213   * @param  caseSensitive  Indicates whether string matching should be
214   *                        case sensitive.
215   */
216  private EqualsJSONObjectFilter(final List<String> field,
217                                 final JSONValue value,
218                                 final boolean caseSensitive)
219  {
220    this.field = field;
221    this.value = value;
222    this.caseSensitive = caseSensitive;
223  }
224
225
226
227  /**
228   * Creates a new instance of this filter type with the provided information.
229   *
230   * @param  field  The name of the top-level field to target with this filter.
231   *                It must not be {@code null} .  See the class-level
232   *                documentation for the {@link JSONObjectFilter} class for
233   *                information about field path specifiers.
234   * @param  value  The target string value for this filter.  It must not be
235   *                {@code null}.
236   */
237  public EqualsJSONObjectFilter(final String field, final String value)
238  {
239    this(Collections.singletonList(field), new JSONString(value));
240  }
241
242
243
244  /**
245   * Creates a new instance of this filter type with the provided information.
246   *
247   * @param  field  The name of the top-level field to target with this filter.
248   *                It must not be {@code null} .  See the class-level
249   *                documentation for the {@link JSONObjectFilter} class for
250   *                information about field path specifiers.
251   * @param  value  The target boolean value for this filter.
252   */
253  public EqualsJSONObjectFilter(final String field, final boolean value)
254  {
255    this(Collections.singletonList(field),
256         (value ? JSONBoolean.TRUE : JSONBoolean.FALSE));
257  }
258
259
260
261  /**
262   * Creates a new instance of this filter type with the provided information.
263   *
264   * @param  field  The name of the top-level field to target with this filter.
265   *                It must not be {@code null} .  See the class-level
266   *                documentation for the {@link JSONObjectFilter} class for
267   *                information about field path specifiers.
268   * @param  value  The target numeric value for this filter.
269   */
270  public EqualsJSONObjectFilter(final String field, final long value)
271  {
272    this(Collections.singletonList(field), new JSONNumber(value));
273  }
274
275
276
277  /**
278   * Creates a new instance of this filter type with the provided information.
279   *
280   * @param  field  The name of the top-level field to target with this filter.
281   *                It must not be {@code null} .  See the class-level
282   *                documentation for the {@link JSONObjectFilter} class for
283   *                information about field path specifiers.
284   * @param  value  The target numeric value for this filter.  It must not be
285   *                {@code null}.
286   */
287  public EqualsJSONObjectFilter(final String field, final double value)
288  {
289    this(Collections.singletonList(field), new JSONNumber(value));
290  }
291
292
293
294  /**
295   * Creates a new instance of this filter type with the provided information.
296   *
297   * @param  field  The name of the top-level field to target with this filter.
298   *                It must not be {@code null} .  See the class-level
299   *                documentation for the {@link JSONObjectFilter} class for
300   *                information about field path specifiers.
301   * @param  value  The target value for this filter.  It must not be
302   *                {@code null}.
303   */
304  public EqualsJSONObjectFilter(final String field, final JSONValue value)
305  {
306    this(Collections.singletonList(field), value);
307  }
308
309
310
311  /**
312   * Creates a new instance of this filter type with the provided information.
313   *
314   * @param  field  The field path specifier for this filter.  It must not be
315   *                {@code null} or empty.  See the class-level documentation
316   *                for the {@link JSONObjectFilter} class for information about
317   *                field path specifiers.
318   * @param  value  The target value for this filter.  It must not be
319   *                {@code null} (although it may be a {@code JSONNull}).
320   */
321  public EqualsJSONObjectFilter(final List<String> field, final JSONValue value)
322  {
323    Validator.ensureNotNull(field);
324    Validator.ensureFalse(field.isEmpty());
325
326    Validator.ensureNotNull(value);
327
328    this.field = Collections.unmodifiableList(new ArrayList<String>(field));
329    this.value = value;
330
331    caseSensitive = false;
332  }
333
334
335
336  /**
337   * Retrieves the field path specifier for this filter.
338   *
339   * @return  The field path specifier for this filter.
340   */
341  public List<String> getField()
342  {
343    return field;
344  }
345
346
347
348  /**
349   * Sets the field path specifier for this filter.
350   *
351   * @param  field  The field path specifier for this filter.  It must not be
352   *                {@code null} or empty.  See the class-level documentation
353   *                for the {@link JSONObjectFilter} class for information about
354   *                field path specifiers.
355   */
356  public void setField(final String... field)
357  {
358    setField(StaticUtils.toList(field));
359  }
360
361
362
363  /**
364   * Sets the field path specifier for this filter.
365   *
366   * @param  field  The field path specifier for this filter.  It must not be
367   *                {@code null} or empty.  See the class-level documentation
368   *                for the {@link JSONObjectFilter} class for information about
369   *                field path specifiers.
370   */
371  public void setField(final List<String> field)
372  {
373    Validator.ensureNotNull(field);
374    Validator.ensureFalse(field.isEmpty());
375
376    this.field = Collections.unmodifiableList(new ArrayList<String>(field));
377  }
378
379
380
381  /**
382   * Retrieves the target value for this filter.
383   *
384   * @return  The target value for this filter.
385   */
386  public JSONValue getValue()
387  {
388    return value;
389  }
390
391
392
393  /**
394   * Specifies the target value for this filter.
395   *
396   * @param  value  The target string value for this filter.  It must not be
397   *                {@code null}.
398   */
399  public void setValue(final String value)
400  {
401    Validator.ensureNotNull(value);
402
403    this.value = new JSONString(value);
404  }
405
406
407
408  /**
409   * Specifies the target value for this filter.
410   *
411   * @param  value  The target Boolean value for this filter.
412   */
413  public void setValue(final boolean value)
414  {
415    this.value = (value ? JSONBoolean.TRUE : JSONBoolean.FALSE);
416  }
417
418
419
420  /**
421   * Specifies the target value for this filter.
422   *
423   * @param  value  The target numeric value for this filter.
424   */
425  public void setValue(final long value)
426  {
427    this.value = new JSONNumber(value);
428  }
429
430
431
432  /**
433   * Specifies the target value for this filter.
434   *
435   * @param  value  The target numeric value for this filter.
436   */
437  public void setValue(final double value)
438  {
439    this.value = new JSONNumber(value);
440  }
441
442
443
444  /**
445   * Specifies the target value for this filter.
446   *
447   * @param  value  The target value for this filter.  It must not be
448   *                {@code null} (although it may be a {@code JSONNull}).
449   */
450  public void setValue(final JSONValue value)
451  {
452    Validator.ensureNotNull(value);
453
454    this.value = value;
455  }
456
457
458
459  /**
460   * Indicates whether string matching should be performed in a case-sensitive
461   * manner.
462   *
463   * @return  {@code true} if string matching should be case sensitive, or
464   *          {@code false} if not.
465   */
466  public boolean caseSensitive()
467  {
468    return caseSensitive;
469  }
470
471
472
473  /**
474   * Specifies whether string matching should be performed in a case-sensitive
475   * manner.
476   *
477   * @param  caseSensitive  Indicates whether string matching should be
478   *                        case sensitive.
479   */
480  public void setCaseSensitive(final boolean caseSensitive)
481  {
482    this.caseSensitive = caseSensitive;
483  }
484
485
486
487  /**
488   * {@inheritDoc}
489   */
490  @Override()
491  public String getFilterType()
492  {
493    return FILTER_TYPE;
494  }
495
496
497
498  /**
499   * {@inheritDoc}
500   */
501  @Override()
502  protected Set<String> getRequiredFieldNames()
503  {
504    return REQUIRED_FIELD_NAMES;
505  }
506
507
508
509  /**
510   * {@inheritDoc}
511   */
512  @Override()
513  protected Set<String> getOptionalFieldNames()
514  {
515    return OPTIONAL_FIELD_NAMES;
516  }
517
518
519
520  /**
521   * {@inheritDoc}
522   */
523  @Override()
524  public boolean matchesJSONObject(final JSONObject o)
525  {
526    final List<JSONValue> candidates = getValues(o, field);
527    if (candidates.isEmpty())
528    {
529      return false;
530    }
531
532    for (final JSONValue v : candidates)
533    {
534      if (value.equals(v, false, (! caseSensitive), false))
535      {
536        return true;
537      }
538
539      if (v instanceof JSONArray)
540      {
541        final JSONArray a = (JSONArray) v;
542        if (a.contains(value, false, (! caseSensitive), false, false))
543        {
544          return true;
545        }
546      }
547    }
548
549    return false;
550  }
551
552
553
554  /**
555   * {@inheritDoc}
556   */
557  @Override()
558  public JSONObject toJSONObject()
559  {
560    final LinkedHashMap<String,JSONValue> fields =
561         new LinkedHashMap<String,JSONValue>(4);
562
563    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
564
565    if (field.size() == 1)
566    {
567      fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0)));
568    }
569    else
570    {
571      final ArrayList<JSONValue> fieldNameValues =
572           new ArrayList<JSONValue>(field.size());
573      for (final String s : field)
574      {
575        fieldNameValues.add(new JSONString(s));
576      }
577      fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues));
578    }
579
580    fields.put(FIELD_VALUE, value);
581
582    if (caseSensitive)
583    {
584      fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE);
585    }
586
587    return new JSONObject(fields);
588  }
589
590
591
592  /**
593   * {@inheritDoc}
594   */
595  @Override()
596  protected EqualsJSONObjectFilter decodeFilter(final JSONObject filterObject)
597            throws JSONException
598  {
599    final List<String> fieldPath =
600         getStrings(filterObject, FIELD_FIELD_PATH, false, null);
601
602    final boolean isCaseSensitive = getBoolean(filterObject,
603         FIELD_CASE_SENSITIVE, false);
604
605    return new EqualsJSONObjectFilter(fieldPath,
606         filterObject.getField(FIELD_VALUE), isCaseSensitive);
607  }
608}