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.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collections;
029    import java.util.HashSet;
030    import java.util.List;
031    import java.util.Set;
032    import java.util.concurrent.ConcurrentHashMap;
033    
034    import com.unboundid.ldap.sdk.Filter;
035    import com.unboundid.util.Debug;
036    import com.unboundid.util.NotExtensible;
037    import com.unboundid.util.StaticUtils;
038    import com.unboundid.util.ThreadSafety;
039    import com.unboundid.util.ThreadSafetyLevel;
040    import com.unboundid.util.json.JSONArray;
041    import com.unboundid.util.json.JSONBoolean;
042    import com.unboundid.util.json.JSONException;
043    import com.unboundid.util.json.JSONObject;
044    import com.unboundid.util.json.JSONString;
045    import com.unboundid.util.json.JSONValue;
046    
047    import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*;
048    
049    
050    
051    /**
052     * <BLOCKQUOTE>
053     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
054     *   LDAP SDK for Java.  It is not available for use in applications that
055     *   include only the Standard Edition of the LDAP SDK, and is not supported for
056     *   use in conjunction with non-UnboundID products.
057     * </BLOCKQUOTE>
058     * This class defines the base class for all JSON object filter types, which are
059     * used to perform matching against JSON objects stored in an UnboundID
060     * Directory Server via the jsonObjectFilterExtensibleMatch matching rule.  The
061     * {@link #toLDAPFilter(String)} method can be used to easily create an LDAP
062     * filter from a JSON object filter.  This filter will have an attribute type
063     * that is the name of an attribute with the JSON object syntax, a matching rule
064     * ID of "jsonObjectFilterExtensibleMatch", and an assertion value that is the
065     * string representation of the JSON object that comprises the filter.  For
066     * example, given the JSON object filter:
067     * <PRE>
068     *   { "filterType" : "equals", "field" : "firstName", "value" : "John" }
069     * </PRE>
070     * the resulting LDAP filter targeting attribute "jsonAttr" would have a string
071     * representation as follows (without the line break that has been added for
072     * formatting purposes):
073     * <PRE>
074     *   (jsonAttr:jsonObjectFilterExtensibleMatch:={ "filterType" : "equals",
075     *   "field" : "firstName", "value" : "John" })
076     * </PRE>
077     * <BR><BR>
078     * JSON object filters are themselves expressed in the form of JSON objects.
079     * All filters must have a "filterType" field that indicates what type of filter
080     * the object represents, and the filter type determines what other fields may
081     * be required or optional for that type of filter.
082     * <BR><BR>
083     * <H2>Types of JSON Object Filters</H2>
084     * This implementation supports a number of different types of filters to use
085     * when matching JSON objects.  Supported JSON object filter types are as
086     * follows:
087     * <H3>Contains Field</H3>
088     * This filter can be used to determine whether a JSON object has a specified
089     * field, optionally with a given type of value.  For example, the following can
090     * be used to determine whether a JSON object contains a top-level field named
091     * "department" that has any kind of value:
092     * <PRE>
093     *   { "filterType" : "containsField",
094     *     "field" : "department" }
095     * </PRE>
096     * <BR>
097     * <H3>Equals</H3>
098     * This filter can be used to determine whether a JSON object has a specific
099     * value for a given field.  For example, the following can be used to determine
100     * whether a JSON object has a top-level field named "firstName" with a value of
101     * "John":
102     * <PRE>
103     *   { "filterType" : "equals",
104     *     "field" : "firstName",
105     *     "value" : "John" }
106     * </PRE>
107     * <BR>
108     * <H3>Equals Any</H3>
109     * This filter can be used to determine whether a JSON object has any of a
110     * number of specified values for a given field.  For example, the following can
111     * be used to determine whether a JSON object has a top-level field named
112     * "userType" with a value that is either "employee", "partner", or
113     * "contractor":
114     * <PRE>
115     *   { "filterType" : "equalsAny",
116     *     "field" : "userType",
117     *     "values" : [  "employee", "partner", "contractor" ] }
118     * </PRE>
119     * <BR>
120     * <H3>Greater Than</H3>
121     * This filter can be used to determine whether a JSON object has a specified
122     * field with a value that is greater than (or optionally greater than or equal
123     * to) a given numeric or string value.  For example, the following filter would
124     * match any JSON object with a top-level field named "salary" with a numeric
125     * value that is greater than or equal to 50000:
126     * <PRE>
127     *   { "filterType" : "greaterThan",
128     *     "field" : "salary",
129     *     "value" : 50000,
130     *     "allowEquals" : true }
131     * </PRE>
132     * <BR>
133     * <H3>Less Than</H3>
134     * This filter can be used to determine whether a JSON object has a specified
135     * field with a value that is less than (or optionally less than or equal to) a
136     * given numeric or string value.  For example, the following filter will match
137     * any JSON object with a "loginFailureCount" field with a numeric value that is
138     * less than or equal to 3.
139     * <PRE>
140     *   { "filterType" : "lessThan",
141     *     "field" : "loginFailureCount",
142     *     "value" : 3,
143     *     "allowEquals" : true }
144     * </PRE>
145     * <BR>
146     * <H3>Substring</H3>
147     * This filter can be used to determine whether a JSON object has a specified
148     * field with a string value that starts with, ends with, and/or contains a
149     * particular substring.  For example, the following filter will match any JSON
150     * object with an "email" field containing a value that ends with
151     * "@example.com":
152     * <PRE>
153     *   { "filterType" : "substring",
154     *     "field" : "email",
155     *     "endsWith" : "@example.com" }
156     * </PRE>
157     * <BR>
158     * <H3>Regular Expression</H3>
159     * This filter can be used to determine whether a JSON object has a specified
160     * field with a string value that matches a given regular expression.  For
161     * example, the following filter can be used to determine whether a JSON object
162     * has a "userID" value that starts with an ASCII letter and contains only
163     * ASCII letters and numeric digits:
164     * <PRE>
165     *   { "filterType" : "regularExpression",
166     *     "field" : "userID",
167     *     "regularExpression" : "^[a-zA-Z][a-zA-Z0-9]*$" }
168     * </PRE>
169     * <BR>
170     * <H3>Object Matches</H3>
171     * This filter can be used to determine whether a JSON object has a specified
172     * field with a value that is itself a JSON object that matches a given JSON
173     * object filter.  For example, the following filter can be used to determine
174     * whether a JSON object has a "contact" field with a value that is a JSON
175     * object with a "type" value of "home" and an "email" field with any kind of
176     * value:
177     * <PRE>
178     *   { "filterType" : "objectMatches",
179     *     "field" : "contact",
180     *     "filter" : {
181     *       "filterType" : "and",
182     *       "andFilters" : [
183     *         { "filterType" : "equals",
184     *           "field" : "type",
185     *           "value" : "home" },
186     *         { "filterType" : "containsField",
187     *           "field" : "email" } ] } }
188     * </PRE>
189     * <BR>
190     * <H3>AND</H3>
191     * This filter can be used to perform a logical AND across a number of filters,
192     * so that the AND filter will only match a JSON object if each of the
193     * encapsulated filters matches that object.  For example, the following filter
194     * could be used to match any JSON object with both a "firstName" field with a
195     * value of "John" and a "lastName" field with a value of "Doe":
196     * <PRE>
197     *   { "filterType" : "and",
198     *     "andFilters" : [
199     *       { "filterType" : "equals",
200     *          "field" : "firstName",
201     *          "value" : "John" },
202     *       { "filterType" : "equals",
203     *          "field" : "lastName",
204     *          "value" : "Doe" } ] }
205     * </PRE>
206     * <BR>
207     * <H3>OR</H3>
208     * This filter can be used to perform a logical OR (or optionally, a logical
209     * exclusive OR) across a number of filters so that the filter will only match
210     * a JSON object if at least one of the encapsulated filters matches that
211     * object.  For example, the following filter could be used to match a JSON
212     * object that has either or both of the "homePhone" or "workPhone" field with
213     * any kind of value:
214     * <PRE>
215     *   { "filterType" : "or",
216     *     "orFilters" : [
217     *       { "filterType" : "containsField",
218     *          "field" : "homePhone" },
219     *       { "filterType" : "containsField",
220     *          "field" : "workPhone" } ] }
221     * </PRE>
222     * <BR>
223     * <H3>Negate</H3>
224     * This filter can be used to negate the result of an encapsulated filter, so
225     * that it will only match a JSON object that the encapsulated filter does not
226     * match.  For example, the following filter will only match JSON objects that
227     * do not have a "userType" field with a value of "employee":
228     * <PRE>
229     *   { "filterType" : "negate",
230     *     "negateFilter" : {
231     *       "filterType" : "equals",
232     *       "field" : "userType",
233     *       "value" : "employee" } }
234     * </PRE>
235     * <BR><BR>
236     * <H2>Targeting Fields in JSON Objects</H2>
237     * Many JSON object filter types need to specify a particular field in the JSON
238     * object that is to be used for the matching.  Unless otherwise specified in
239     * the Javadoc documentation for a particular filter type, the target field
240     * should be specified either as a single string (to target a top-level field in
241     * the object) or a non-empty array of strings (to provide the complete path to
242     * the target field).  In the case the target field is specified in an array,
243     * the first (leftmost in the string representation) element of the array will
244     * specify a top-level field in the JSON object.  If the array contains a second
245     * element, then that indicates that one of the following should be true:
246     * <UL>
247     *   <LI>
248     *     The top-level field specified by the first element should have a value
249     *     that is itself a JSON object, and the second element specifies the name
250     *     of a field in that JSON object.
251     *   </LI>
252     *   <LI>
253     *     The top-level field specified by the first element should have a value
254     *     that is an array, and at least one element of the array is a JSON object
255     *     with a field whose name matches the second element of the field path
256     *     array.
257     *   </LI>
258     * </UL>
259     * Each additional element of the field path array specifies an additional level
260     * of hierarchy in the JSON object.  For example, consider the following JSON
261     * object:
262     * <PRE>
263     *   { "field1" : "valueA",
264     *     "field2" : {
265     *       "field3" : "valueB",
266     *       "field4" : {
267     *         "field5" : "valueC" } } }
268     * </PRE>
269     * In the above example, the field whose value is {@code "valueA"} can be
270     * targeted using either {@code "field1"} or {@code [ "field1" ]}.  The field
271     * whose value is {@code "valueB"} can be targeted as
272     * {@code [ "field2", "field3" ]}.  The field whose value is {@code "valueC"}
273     * can be targeted as {@code [ "field2", "field4", "field5" ]}.
274     * <BR><BR>
275     * Note that the mechanism outlined here cannot always be used to uniquely
276     * identify each field in a JSON object.  In particular, if an array contains
277     * multiple JSON objects, then it is possible that some of those JSON objects
278     * could have field names in common, and therefore the same field path reference
279     * could apply to multiple fields.  For example, in the JSON object:
280     * <PRE>
281     *   {
282     *     "contact" : [
283     *       { "type" : "Home",
284     *         "email" : "jdoe@example.net",
285     *         "phone" : "123-456-7890" },
286     *       { "type" : "Work",
287     *         "email" : "john.doe@example.com",
288     *         "phone" : "789-456-0123" } ] }
289     * </PRE>
290     * The field specifier {@code [ "contact", "type" ]} can reference either the
291     * field whose value is {@code "Home"} or the field whose value is
292     * {@code "Work"}.  The field specifier {@code [ "contact", "email" ]} can
293     * reference the field whose value is {@code "jdoe@example.net"} or the field
294     * whose value is {@code "john.doe@example.com"}.  And the field specifier
295     * {@code [ "contact", "phone" ]} can reference the field with value
296     * {@code "123-456-7890"} or the field with value {@code "789-456-0123"}.  This
297     * ambiguity is intentional for values in arrays because it makes it possible
298     * to target array elements without needing to know the order of elements in the
299     * array.
300     * <BR><BR>
301     * <H2>Thread Safety of JSON Object Filters</H2>
302     * JSON object filters are not guaranteed to be threadsafe.  Because some filter
303     * types support a number of configurable options, it is more convenient and
304     * future-proof to provide minimal constructors to specify values for the
305     * required fields and setter methods for the optional fields.  These filters
306     * will be mutable, and any filter that may be altered should not be accessed
307     * concurrently by multiple threads.  However, if a JSON object filter is not
308     * expected to be altered, then it may safely be shared across multiple threads.
309     * Further, LDAP filters created using the {@link #toLDAPFilter} method and
310     * JSON objects created using the {@link #toJSONObject} method will be
311     * threadsafe under all circumstances.
312     */
313    @NotExtensible()
314    @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
315    public abstract class JSONObjectFilter
316           implements Serializable
317    {
318      /**
319       * The name of the matching rule that may be used to determine whether an
320       * attribute value matches a JSON object filter.
321       */
322      public static final String JSON_OBJECT_FILTER_MATCHING_RULE_NAME =
323           "jsonObjectFilterExtensibleMatch";
324    
325    
326    
327      /**
328       * The numeric OID of the matching rule that may be used to determine whether
329       * an attribute value matches a JSON object filter.
330       */
331      public static final String JSON_OBJECT_FILTER_MATCHING_RULE_OID =
332           "1.3.6.1.4.1.30221.2.4.13";
333    
334    
335    
336      /**
337       * The name of the JSON field that is used to specify the filter type for
338       * the JSON object filter.
339       */
340      public static final String FIELD_FILTER_TYPE = "filterType";
341    
342    
343    
344      /**
345       * A map of filter type names to instances that can be used for decoding JSON
346       * objects to filters of that type.
347       */
348      private static final ConcurrentHashMap<String,JSONObjectFilter> FILTER_TYPES =
349           new ConcurrentHashMap<String,JSONObjectFilter>(10);
350      static
351      {
352        registerFilterType(
353             new ContainsFieldJSONObjectFilter(),
354             new EqualsJSONObjectFilter(),
355             new EqualsAnyJSONObjectFilter(),
356             new ObjectMatchesJSONObjectFilter(),
357             new SubstringJSONObjectFilter(),
358             new GreaterThanJSONObjectFilter(),
359             new LessThanJSONObjectFilter(),
360             new RegularExpressionJSONObjectFilter(),
361             new ANDJSONObjectFilter(),
362             new ORJSONObjectFilter(),
363             new NegateJSONObjectFilter());
364      }
365    
366    
367    
368      /**
369       * The serial version UID for this serializable class.
370       */
371      private static final long serialVersionUID = -551616596693584562L;
372    
373    
374    
375      /**
376       * Retrieves the value that must appear in the {@code filterType} field for
377       * this filter.
378       *
379       * @return  The value that must appear in the {@code filterType} field for
380       *          this filter.
381       */
382      public abstract String getFilterType();
383    
384    
385    
386      /**
387       * Retrieves the names of all fields (excluding the {@code filterType} field)
388       * that must be present in the JSON object representing a filter of this type.
389       *
390       * @return  The names of all fields (excluding the {@code filterType} field)
391       *          that must be present in the JSON object representing a filter of
392       *          this type.
393       */
394      protected abstract Set<String> getRequiredFieldNames();
395    
396    
397    
398      /**
399       * Retrieves the names of all fields that may optionally be present but are
400       * not required in the JSON object representing a filter of this type.
401       *
402       * @return  The names of all fields that may optionally be present but are not
403       *          required in the JSON object representing a filter of this type.
404       */
405      protected abstract Set<String> getOptionalFieldNames();
406    
407    
408    
409      /**
410       * Indicates whether this JSON object filter matches the provided JSON object.
411       *
412       * @param  o  The JSON object for which to make the determination.
413       *
414       * @return  {@code true} if this JSON object filter matches the provided JSON
415       *          object, or {@code false} if not.
416       */
417      public abstract boolean matchesJSONObject(final JSONObject o);
418    
419    
420    
421      /**
422       * Retrieves a JSON object that represents this filter.
423       *
424       * @return  A JSON object that represents this filter.
425       */
426      public abstract JSONObject toJSONObject();
427    
428    
429    
430      /**
431       * Retrieves the value of the specified field from the provided JSON object as
432       * a list of strings.  The specified field must be a top-level field in the
433       * JSON object, and it must have a value that is a single string or an array
434       * of strings.
435       *
436       * @param  o              The JSON object to examine.  It must not be
437       *                        {@code null}.
438       * @param  fieldName      The name of a top-level field in the JSON object
439       *                        that is expected to have a value that is a string
440       *                        or an array of strings.  It must not be
441       *                        {@code null}.  It will be treated in a
442       *                        case-sensitive manner.
443       * @param  allowEmpty     Indicates whether the value is allowed to be an
444       *                        empty array.
445       * @param  defaultValues  The list of default values to return if the field
446       *                        is not present.  If this is {@code null}, then a
447       *                        {@code JSONException} will be thrown if the
448       *                        specified field is not present.
449       *
450       * @return  The list of strings retrieved from the JSON object, or the
451       *          default list if the field is not present in the object.
452       *
453       * @throws  JSONException  If the object doesn't have the specified field and
454       *                         no set of default values was provided, or if the
455       *                         value of the specified field was not a string or
456       *                         an array of strings.
457       */
458      protected List<String> getStrings(final JSONObject o, final String fieldName,
459                                        final boolean allowEmpty,
460                                        final List<String> defaultValues)
461                throws JSONException
462      {
463        final JSONValue v = o.getField(fieldName);
464        if (v == null)
465        {
466          if (defaultValues == null)
467          {
468            throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
469                 String.valueOf(o), getFilterType(), fieldName));
470          }
471          else
472          {
473            return defaultValues;
474          }
475        }
476    
477        if (v instanceof JSONString)
478        {
479          return Arrays.asList(((JSONString) v).stringValue());
480        }
481        else if (v instanceof JSONArray)
482        {
483          final List<JSONValue> values = ((JSONArray) v).getValues();
484          if (values.isEmpty())
485          {
486            if (allowEmpty)
487            {
488              return Collections.emptyList();
489            }
490            else
491            {
492              throw new JSONException(ERR_OBJECT_FILTER_VALUE_EMPTY_ARRAY.get(
493                   String.valueOf(o), getFilterType(), fieldName));
494            }
495          }
496    
497          final ArrayList<String> valueList = new ArrayList<String>(values.size());
498          for (final JSONValue av : values)
499          {
500            if (av instanceof JSONString)
501            {
502              valueList.add(((JSONString) av).stringValue());
503            }
504            else
505            {
506              throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_STRINGS.get(
507                   String.valueOf(o), getFilterType(), fieldName));
508            }
509          }
510          return valueList;
511        }
512        else
513        {
514          throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_STRINGS.get(
515               String.valueOf(o), getFilterType(), fieldName));
516        }
517      }
518    
519    
520    
521      /**
522       * Retrieves the value of the specified field from the provided JSON object as
523       * a strings.  The specified field must be a top-level field in the JSON
524       * object, and it must have a value that is a single string.
525       *
526       * @param  o             The JSON object to examine.  It must not be
527       *                       {@code null}.
528       * @param  fieldName     The name of a top-level field in the JSON object
529       *                       that is expected to have a value that is a string.
530       *                       It must not be {@code null}.  It will be treated in a
531       *                       case-sensitive manner.
532       * @param  defaultValue  The default values to return if the field is not
533       *                       present.  If this is {@code null} and
534       *                       {@code required} is {@code true}, then a
535       *                       {@code JSONException} will be thrown if the specified
536       *                       field is not present.
537       * @param  required      Indicates whether the field is required to be present
538       *                       in the object.
539       *
540       * @return  The string retrieved from the JSON object, or the default value if
541       *          the field is not present in the object.
542       *
543       * @throws  JSONException  If the object doesn't have the specified field, the
544       *                         field is required, and no default value was
545       *                         provided, or if the value of the specified field
546       *                         was not a string.
547       */
548      protected String getString(final JSONObject o, final String fieldName,
549                                 final String defaultValue, final boolean required)
550                throws JSONException
551      {
552        final JSONValue v = o.getField(fieldName);
553        if (v == null)
554        {
555          if (required && (defaultValue == null))
556          {
557            throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
558                 String.valueOf(o), getFilterType(), fieldName));
559          }
560          else
561          {
562            return defaultValue;
563          }
564        }
565    
566        if (v instanceof JSONString)
567        {
568          return ((JSONString) v).stringValue();
569        }
570        else
571        {
572          throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_STRING.get(
573               String.valueOf(o), getFilterType(), fieldName));
574        }
575      }
576    
577    
578    
579      /**
580       * Retrieves the value of the specified field from the provided JSON object as
581       * a {@code boolean}.  The specified field must be a top-level field in the
582       * JSON object, and it must have a value that is either {@code true} or
583       * {@code false}.
584       *
585       * @param  o             The JSON object to examine.  It must not be
586       *                       {@code null}.
587       * @param  fieldName     The name of a top-level field in the JSON object that
588       *                       that is expected to have a value that is either
589       *                       {@code true} or {@code false}.
590       * @param  defaultValue  The default value to return if the specified field
591       *                       is not present in the JSON object.  If this is
592       *                       {@code null}, then a {@code JSONException} will be
593       *                       thrown if the specified field is not present.
594       *
595       * @return  The value retrieved from the JSON object, or the default value if
596       *          the field is not present in the object.
597       *
598       * @throws  JSONException  If the object doesn't have the specified field and
599       *                         no default value was provided, or if the value of
600       *                         the specified field was neither {@code true} nor
601       *                         {@code false}.
602       */
603      protected boolean getBoolean(final JSONObject o, final String fieldName,
604                                   final Boolean defaultValue)
605                throws JSONException
606      {
607        final JSONValue v = o.getField(fieldName);
608        if (v == null)
609        {
610          if (defaultValue == null)
611          {
612            throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
613                 String.valueOf(o), getFilterType(), fieldName));
614          }
615          else
616          {
617            return defaultValue;
618          }
619        }
620    
621        if (v instanceof JSONBoolean)
622        {
623          return ((JSONBoolean) v).booleanValue();
624        }
625        else
626        {
627          throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_BOOLEAN.get(
628               String.valueOf(o), getFilterType(), fieldName));
629        }
630      }
631    
632    
633    
634      /**
635       * Retrieves the value of the specified field from the provided JSON object as
636       * a list of JSON object filters.  The specified field must be a top-level
637       * field in the JSON object and it must have a value that is an array of
638       * JSON objects that represent valid JSON object filters.
639       *
640       * @param  o          The JSON object to examine.  It must not be
641       *                    {@code null}.
642       * @param  fieldName  The name of a top-level field in the JSON object that is
643       *                    expected to have a value that is an array of JSON
644       *                    objects that represent valid JSON object filters.  It
645       *                    must not be {@code null}.
646       *
647       * @return  The list of JSON object filters retrieved from the JSON object.
648       *
649       * @throws  JSONException  If the object doesn't have the specified field, or
650       *                         if the value of that field is not an array of
651       *                         JSON objects that represent valid JSON object
652       *                         filters.
653       */
654      protected List<JSONObjectFilter> getFilters(final JSONObject o,
655                                                  final String fieldName)
656                throws JSONException
657      {
658        final JSONValue value = o.getField(fieldName);
659        if (value == null)
660        {
661          throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
662               String.valueOf(o), getFilterType(), fieldName));
663        }
664    
665        if (! (value instanceof JSONArray))
666        {
667          throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_ARRAY.get(
668               String.valueOf(o), getFilterType(), fieldName));
669        }
670    
671        final List<JSONValue> values = ((JSONArray) value).getValues();
672        final ArrayList<JSONObjectFilter> filterList =
673             new ArrayList<JSONObjectFilter>(values.size());
674        for (final JSONValue arrayValue : values)
675        {
676          if (! (arrayValue instanceof JSONObject))
677          {
678            throw new JSONException(ERR_OBJECT_FILTER_ARRAY_ELEMENT_NOT_OBJECT.get(
679                 String.valueOf(o), getFilterType(), fieldName));
680          }
681    
682          final JSONObject filterObject = (JSONObject) arrayValue;
683          try
684          {
685            filterList.add(decode(filterObject));
686          }
687          catch (final JSONException e)
688          {
689            Debug.debugException(e);
690            throw new JSONException(
691                 ERR_OBJECT_FILTER_ARRAY_ELEMENT_NOT_FILTER.get(String.valueOf(o),
692                      getFilterType(), String.valueOf(filterObject), fieldName,
693                      e.getMessage()),
694                 e);
695          }
696        }
697    
698        return filterList;
699      }
700    
701    
702    
703      /**
704       * Retrieves the set of values that match the provided field name specifier.
705       *
706       * @param  o          The JSON object to examine.
707       * @param  fieldName  The field name specifier for the values to retrieve.
708       *
709       * @return  The set of values that match the provided field name specifier, or
710       *          an empty list if the provided JSON object does not have any fields
711       *          matching the provided specifier.
712       */
713      protected static List<JSONValue> getValues(final JSONObject o,
714                                                 final List<String> fieldName)
715      {
716        final ArrayList<JSONValue> values = new ArrayList<JSONValue>(10);
717        getValues(o, fieldName, 0, values);
718        return values;
719      }
720    
721    
722    
723      /**
724       * Retrieves the set of values that match the provided field name specifier.
725       *
726       * @param  o               The JSON object to examine.
727       * @param  fieldName       The field name specifier for the values to
728       *                         retrieve.
729       * @param  fieldNameIndex  The current index into the field name specifier.
730       * @param  values          The list into which matching values should be
731       *                         added.
732       */
733      private static void getValues(final JSONObject o,
734                                    final List<String> fieldName,
735                                    final int fieldNameIndex,
736                                    final List<JSONValue> values)
737      {
738        final JSONValue v = o.getField(fieldName.get(fieldNameIndex));
739        if (v == null)
740        {
741          return;
742        }
743    
744        final int nextIndex = fieldNameIndex + 1;
745        if (nextIndex < fieldName.size())
746        {
747          // This indicates that there are more elements in the field name
748          // specifier.  The value must either be a JSON object that we can look
749          // further into, or it must be an array containing one or more JSON
750          // objects.
751          if (v instanceof JSONObject)
752          {
753            getValues((JSONObject) v, fieldName, nextIndex, values);
754          }
755          else if (v instanceof JSONArray)
756          {
757            getValuesFromArray((JSONArray) v, fieldName, nextIndex, values);
758          }
759    
760          return;
761        }
762    
763        // If we've gotten here, then there is no more of the field specifier, so
764        // the value we retrieved matches the specifier.  Add it to the list of
765        // values.
766        values.add(v);
767      }
768    
769    
770    
771      /**
772       * Calls {@code getValues} for any elements of the provided array that are
773       * JSON objects, recursively descending into any nested arrays.
774       *
775       * @param  a               The array to process.
776       * @param  fieldName       The field name specifier for the values to
777       *                         retrieve.
778       * @param  fieldNameIndex  The current index into the field name specifier.
779       * @param  values          The list into which matching values should be
780       *                         added.
781       */
782      private static void getValuesFromArray(final JSONArray a,
783                                             final List<String> fieldName,
784                                             final int fieldNameIndex,
785                                             final List<JSONValue> values)
786      {
787        for (final JSONValue v : a.getValues())
788        {
789          if (v instanceof JSONObject)
790          {
791            getValues((JSONObject) v, fieldName, fieldNameIndex, values);
792          }
793          else if (v instanceof JSONArray)
794          {
795            getValuesFromArray((JSONArray) v, fieldName, fieldNameIndex, values);
796          }
797        }
798      }
799    
800    
801    
802      /**
803       * Decodes the provided JSON object as a JSON object filter.
804       *
805       * @param  o  The JSON object to be decoded as a JSON object filter.
806       *
807       * @return  The JSON object filter decoded from the provided JSON object.
808       *
809       * @throws  JSONException  If the provided JSON object cannot be decoded as a
810       *                         JSON object filter.
811       */
812      public static JSONObjectFilter decode(final JSONObject o)
813             throws JSONException
814      {
815        // Get the value of the filter type field for the object and use it to get
816        // a filter instance we can use to decode filters of that type.
817        final JSONValue filterTypeValue = o.getField(FIELD_FILTER_TYPE);
818        if (filterTypeValue == null)
819        {
820          throw new JSONException(ERR_OBJECT_FILTER_MISSING_FILTER_TYPE.get(
821               String.valueOf(o), FIELD_FILTER_TYPE));
822        }
823    
824        if (! (filterTypeValue instanceof JSONString))
825        {
826          throw new JSONException(ERR_OBJECT_FILTER_INVALID_FILTER_TYPE.get(
827               String.valueOf(o), FIELD_FILTER_TYPE));
828        }
829    
830        final String filterType =
831             StaticUtils.toLowerCase(((JSONString) filterTypeValue).stringValue());
832        final JSONObjectFilter decoder = FILTER_TYPES.get(filterType);
833        if (decoder == null)
834        {
835          throw new JSONException(ERR_OBJECT_FILTER_INVALID_FILTER_TYPE.get(
836               String.valueOf(o), FIELD_FILTER_TYPE));
837        }
838    
839    
840        // Validate the set of fields contained in the provided object to ensure
841        // that all required fields were provided and that no disallowed fields were
842        // included.
843        final HashSet<String> objectFields =
844             new HashSet<String>(o.getFields().keySet());
845        objectFields.remove(FIELD_FILTER_TYPE);
846        for (final String requiredField : decoder.getRequiredFieldNames())
847        {
848          if (! objectFields.remove(requiredField))
849          {
850            throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
851                 String.valueOf(o), decoder.getFilterType(), requiredField));
852          }
853        }
854    
855        for (final String remainingField : objectFields)
856        {
857          if (! decoder.getOptionalFieldNames().contains(remainingField))
858          {
859            throw new JSONException(ERR_OBJECT_FILTER_UNRECOGNIZED_FIELD.get(
860                 String.valueOf(o), decoder.getFilterType(), remainingField));
861          }
862        }
863    
864        return decoder.decodeFilter(o);
865      }
866    
867    
868    
869      /**
870       * Decodes the provided JSON object as a filter of this type.
871       *
872       * @param  o  The JSON object to be decoded.  The caller will have already
873       *            validated that all required fields are present, and that it
874       *            does not have any fields that are neither required nor optional.
875       *
876       * @return  The decoded JSON object filter.
877       *
878       * @throws  JSONException  If the provided JSON object cannot be decoded as a
879       *                         valid filter of this type.
880       */
881      protected abstract JSONObjectFilter decodeFilter(final JSONObject o)
882                throws JSONException;
883    
884    
885    
886      /**
887       * Registers the provided filter type(s) so that this class can decode filters
888       * of that type.
889       *
890       * @param  impl  The filter type implementation(s) to register.
891       */
892      protected static void registerFilterType(final JSONObjectFilter... impl)
893      {
894        for (final JSONObjectFilter f : impl)
895        {
896          final String filterTypeName = StaticUtils.toLowerCase(f.getFilterType());
897          FILTER_TYPES.put(filterTypeName, f);
898        }
899      }
900    
901    
902    
903      /**
904       * Constructs an LDAP extensible matching filter that may be used to identify
905       * entries with one or more values for a specified attribute that represent
906       * JSON objects matching this JSON object filter.
907       *
908       * @param  attributeDescription  The attribute description (i.e., the
909       *                               attribute name or numeric OID plus zero or
910       *                               more attribute options) for the LDAP
911       *                               attribute to target with this filter.  It
912       *                               must not be {@code null}.
913       *
914       * @return  The constructed LDAP extensible matching filter.
915       */
916      public final Filter toLDAPFilter(final String attributeDescription)
917      {
918        return Filter.createExtensibleMatchFilter(attributeDescription,
919             JSON_OBJECT_FILTER_MATCHING_RULE_NAME, false, toString());
920      }
921    
922    
923    
924      /**
925       * Retrieves a hash code for this JSON object filter.
926       *
927       * @return  A hash code for this JSON object filter.
928       */
929      @Override()
930      public final int hashCode()
931      {
932        return toJSONObject().hashCode();
933      }
934    
935    
936    
937      /**
938       * Indicates whether the provided object is considered equal to this JSON
939       * object filter.
940       *
941       * @param  o  The object for which to make the determination.
942       *
943       * @return  {@code true} if the provided object is considered equal to this
944       *          JSON object filter, or {@code false} if not.
945       */
946      @Override()
947      public final boolean equals(final Object o)
948      {
949        if (o == this)
950        {
951          return true;
952        }
953    
954        if (o instanceof JSONObjectFilter)
955        {
956          final JSONObjectFilter f = (JSONObjectFilter) o;
957          return toJSONObject().equals(f.toJSONObject());
958        }
959    
960        return false;
961      }
962    
963    
964    
965      /**
966       * Retrieves a string representation of the JSON object that represents this
967       * filter.
968       *
969       * @return  A string representation of the JSON object that represents this
970       *          filter.
971       */
972      @Override()
973      public final String toString()
974      {
975        return toJSONObject().toString();
976      }
977    
978    
979    
980      /**
981       * Appends a string representation of the JSON object that represents this
982       * filter to the provided buffer.
983       *
984       * @param  buffer  The buffer to which the information should be appended.
985       */
986      public final void toString(final StringBuilder buffer)
987      {
988        toJSONObject().toString(buffer);
989      }
990    }