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