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