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