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