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