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