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