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