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