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 }