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