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.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.LinkedHashMap; 030import java.util.List; 031import java.util.Set; 032 033import com.unboundid.util.Mutable; 034import com.unboundid.util.StaticUtils; 035import com.unboundid.util.ThreadSafety; 036import com.unboundid.util.ThreadSafetyLevel; 037import com.unboundid.util.Validator; 038import com.unboundid.util.json.JSONArray; 039import com.unboundid.util.json.JSONBoolean; 040import com.unboundid.util.json.JSONException; 041import com.unboundid.util.json.JSONNumber; 042import com.unboundid.util.json.JSONObject; 043import com.unboundid.util.json.JSONString; 044import com.unboundid.util.json.JSONValue; 045 046 047 048/** 049 * This class provides an implementation of a JSON object filter that can be 050 * used to identify JSON objects that have a particular value for a specified 051 * field. 052 * <BR> 053 * <BLOCKQUOTE> 054 * <B>NOTE:</B> This class, and other classes within the 055 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 056 * supported for use against Ping Identity, UnboundID, and 057 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 058 * for proprietary functionality or for external specifications that are not 059 * considered stable or mature enough to be guaranteed to work in an 060 * interoperable way with other types of LDAP servers. 061 * </BLOCKQUOTE> 062 * <BR> 063 * The fields that are required to be included in an "equals" filter are: 064 * <UL> 065 * <LI> 066 * {@code fieldName} -- A field path specifier for the JSON field for which 067 * to make the determination. This may be either a single string or an 068 * array of strings as described in the "Targeting Fields in JSON Objects" 069 * section of the class-level documentation for {@link JSONObjectFilter}. 070 * </LI> 071 * <LI> 072 * {@code value} -- The value to match. This value may be of any type. In 073 * order for a JSON object to match the equals filter, the value of the 074 * target field must either have the same type value as this value, or the 075 * value of the target field must be an array containing at least one 076 * element with the same type and value. If the provided value is an array, 077 * then the order, types, and values of the array must match an array 078 * contained in the target field. If the provided value is a JSON object, 079 * then the target field must contain a JSON object with exactly the same 080 * set of fields and values. 081 * </LI> 082 * </UL> 083 * The fields that may optionally be included in an "equals" filter are: 084 * <UL> 085 * <LI> 086 * {@code caseSensitive} -- Indicates whether string values should be 087 * treated in a case-sensitive manner. If present, this field must have a 088 * Boolean value of either {@code true} or {@code false}. If it is not 089 * provided, then a default value of {@code false} will be assumed so that 090 * strings are treated in a case-insensitive manner. 091 * </LI> 092 * </UL> 093 * <H2>Examples</H2> 094 * The following is an example of an "equals" filter that will match any JSON 095 * object with a top-level field named "firstName" with a value of "John": 096 * <PRE> 097 * { "filterType" : "equals", 098 * "field" : "firstName", 099 * "value" : "John" } 100 * </PRE> 101 * The above filter can be created with the code: 102 * <PRE> 103 * EqualsJSONObjectFilter filter = 104 * new EqualsJSONObjectFilter("firstName", "John"); 105 * </PRE> 106 * The following is an example of an "equals" filter that will match a JSON 107 * object with a top-level field named "contact" whose value is a JSON object 108 * (or an array containing one or more JSON objects) with a field named "type" 109 * and a value of "home": 110 * <PRE> 111 * { "filterType" : "equals", 112 * "field" : [ "contact", "type" ], 113 * "value" : "home" } 114 * </PRE> 115 * That filter can be created with the code: 116 * <PRE> 117 * EqualsJSONObjectFilter filter = 118 * new EqualsJSONObjectFilter(Arrays.asList("contact", "type"), "Home"); 119 * </PRE> 120 */ 121@Mutable() 122@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 123public final class EqualsJSONObjectFilter 124 extends JSONObjectFilter 125{ 126 /** 127 * The value that should be used for the filterType element of the JSON object 128 * that represents an "equals" filter. 129 */ 130 public static final String FILTER_TYPE = "equals"; 131 132 133 134 /** 135 * The name of the JSON field that is used to specify the field in the target 136 * JSON object for which to make the determination. 137 */ 138 public static final String FIELD_FIELD_PATH = "field"; 139 140 141 142 /** 143 * The name of the JSON field that is used to specify the value to use for 144 * the matching. 145 */ 146 public static final String FIELD_VALUE = "value"; 147 148 149 150 /** 151 * The name of the JSON field that is used to indicate whether string matching 152 * should be case-sensitive. 153 */ 154 public static final String FIELD_CASE_SENSITIVE = "caseSensitive"; 155 156 157 158 /** 159 * The pre-allocated set of required field names. 160 */ 161 private static final Set<String> REQUIRED_FIELD_NAMES = 162 Collections.unmodifiableSet(new HashSet<>( 163 Arrays.asList(FIELD_FIELD_PATH, FIELD_VALUE))); 164 165 166 167 /** 168 * The pre-allocated set of optional field names. 169 */ 170 private static final Set<String> OPTIONAL_FIELD_NAMES = 171 Collections.unmodifiableSet(new HashSet<>( 172 Collections.singletonList(FIELD_CASE_SENSITIVE))); 173 174 175 176 /** 177 * The serial version UID for this serializable class. 178 */ 179 private static final long serialVersionUID = 4622567662624840125L; 180 181 182 183 // Indicates whether string matching should be case-sensitive. 184 private volatile boolean caseSensitive; 185 186 // The expected value for the target field. 187 private volatile JSONValue value; 188 189 // The path name specifier for the target field. 190 private volatile List<String> field; 191 192 193 194 /** 195 * Creates an instance of this filter type that can only be used for decoding 196 * JSON objects as "equals" filters. It cannot be used as a regular "equals" 197 * filter. 198 */ 199 EqualsJSONObjectFilter() 200 { 201 field = null; 202 value = null; 203 caseSensitive = false; 204 } 205 206 207 208 /** 209 * Creates a new instance of this filter type with the provided information. 210 * 211 * @param field The path name specifier for the target field. 212 * @param value The expected value for the target field. 213 * @param caseSensitive Indicates whether string matching should be 214 * case sensitive. 215 */ 216 private EqualsJSONObjectFilter(final List<String> field, 217 final JSONValue value, 218 final boolean caseSensitive) 219 { 220 this.field = field; 221 this.value = value; 222 this.caseSensitive = caseSensitive; 223 } 224 225 226 227 /** 228 * Creates a new instance of this filter type with the provided information. 229 * 230 * @param field The name of the top-level field to target with this filter. 231 * It must not be {@code null} . See the class-level 232 * documentation for the {@link JSONObjectFilter} class for 233 * information about field path specifiers. 234 * @param value The target string value for this filter. It must not be 235 * {@code null}. 236 */ 237 public EqualsJSONObjectFilter(final String field, final String value) 238 { 239 this(Collections.singletonList(field), new JSONString(value)); 240 } 241 242 243 244 /** 245 * Creates a new instance of this filter type with the provided information. 246 * 247 * @param field The name of the top-level field to target with this filter. 248 * It must not be {@code null} . See the class-level 249 * documentation for the {@link JSONObjectFilter} class for 250 * information about field path specifiers. 251 * @param value The target boolean value for this filter. 252 */ 253 public EqualsJSONObjectFilter(final String field, final boolean value) 254 { 255 this(Collections.singletonList(field), 256 (value ? JSONBoolean.TRUE : JSONBoolean.FALSE)); 257 } 258 259 260 261 /** 262 * Creates a new instance of this filter type with the provided information. 263 * 264 * @param field The name of the top-level field to target with this filter. 265 * It must not be {@code null} . See the class-level 266 * documentation for the {@link JSONObjectFilter} class for 267 * information about field path specifiers. 268 * @param value The target numeric value for this filter. 269 */ 270 public EqualsJSONObjectFilter(final String field, final long value) 271 { 272 this(Collections.singletonList(field), new JSONNumber(value)); 273 } 274 275 276 277 /** 278 * Creates a new instance of this filter type with the provided information. 279 * 280 * @param field The name of the top-level field to target with this filter. 281 * It must not be {@code null} . See the class-level 282 * documentation for the {@link JSONObjectFilter} class for 283 * information about field path specifiers. 284 * @param value The target numeric value for this filter. It must not be 285 * {@code null}. 286 */ 287 public EqualsJSONObjectFilter(final String field, final double value) 288 { 289 this(Collections.singletonList(field), new JSONNumber(value)); 290 } 291 292 293 294 /** 295 * Creates a new instance of this filter type with the provided information. 296 * 297 * @param field The name of the top-level field to target with this filter. 298 * It must not be {@code null} . See the class-level 299 * documentation for the {@link JSONObjectFilter} class for 300 * information about field path specifiers. 301 * @param value The target value for this filter. It must not be 302 * {@code null}. 303 */ 304 public EqualsJSONObjectFilter(final String field, final JSONValue value) 305 { 306 this(Collections.singletonList(field), value); 307 } 308 309 310 311 /** 312 * Creates a new instance of this filter type with the provided information. 313 * 314 * @param field The field path specifier for this filter. It must not be 315 * {@code null} or empty. See the class-level documentation 316 * for the {@link JSONObjectFilter} class for information about 317 * field path specifiers. 318 * @param value The target value for this filter. It must not be 319 * {@code null} (although it may be a {@code JSONNull}). 320 */ 321 public EqualsJSONObjectFilter(final List<String> field, final JSONValue value) 322 { 323 Validator.ensureNotNull(field); 324 Validator.ensureFalse(field.isEmpty()); 325 326 Validator.ensureNotNull(value); 327 328 this.field = Collections.unmodifiableList(new ArrayList<>(field)); 329 this.value = value; 330 331 caseSensitive = false; 332 } 333 334 335 336 /** 337 * Retrieves the field path specifier for this filter. 338 * 339 * @return The field path specifier for this filter. 340 */ 341 public List<String> getField() 342 { 343 return field; 344 } 345 346 347 348 /** 349 * Sets the field path specifier for this filter. 350 * 351 * @param field The field path specifier for this filter. It must not be 352 * {@code null} or empty. See the class-level documentation 353 * for the {@link JSONObjectFilter} class for information about 354 * field path specifiers. 355 */ 356 public void setField(final String... field) 357 { 358 setField(StaticUtils.toList(field)); 359 } 360 361 362 363 /** 364 * Sets the field path specifier for this filter. 365 * 366 * @param field The field path specifier for this filter. It must not be 367 * {@code null} or empty. See the class-level documentation 368 * for the {@link JSONObjectFilter} class for information about 369 * field path specifiers. 370 */ 371 public void setField(final List<String> field) 372 { 373 Validator.ensureNotNull(field); 374 Validator.ensureFalse(field.isEmpty()); 375 376 this.field = Collections.unmodifiableList(new ArrayList<>(field)); 377 } 378 379 380 381 /** 382 * Retrieves the target value for this filter. 383 * 384 * @return The target value for this filter. 385 */ 386 public JSONValue getValue() 387 { 388 return value; 389 } 390 391 392 393 /** 394 * Specifies the target value for this filter. 395 * 396 * @param value The target string value for this filter. It must not be 397 * {@code null}. 398 */ 399 public void setValue(final String value) 400 { 401 Validator.ensureNotNull(value); 402 403 this.value = new JSONString(value); 404 } 405 406 407 408 /** 409 * Specifies the target value for this filter. 410 * 411 * @param value The target Boolean value for this filter. 412 */ 413 public void setValue(final boolean value) 414 { 415 this.value = (value ? JSONBoolean.TRUE : JSONBoolean.FALSE); 416 } 417 418 419 420 /** 421 * Specifies the target value for this filter. 422 * 423 * @param value The target numeric value for this filter. 424 */ 425 public void setValue(final long value) 426 { 427 this.value = new JSONNumber(value); 428 } 429 430 431 432 /** 433 * Specifies the target value for this filter. 434 * 435 * @param value The target numeric value for this filter. 436 */ 437 public void setValue(final double value) 438 { 439 this.value = new JSONNumber(value); 440 } 441 442 443 444 /** 445 * Specifies the target value for this filter. 446 * 447 * @param value The target value for this filter. It must not be 448 * {@code null} (although it may be a {@code JSONNull}). 449 */ 450 public void setValue(final JSONValue value) 451 { 452 Validator.ensureNotNull(value); 453 454 this.value = value; 455 } 456 457 458 459 /** 460 * Indicates whether string matching should be performed in a case-sensitive 461 * manner. 462 * 463 * @return {@code true} if string matching should be case sensitive, or 464 * {@code false} if not. 465 */ 466 public boolean caseSensitive() 467 { 468 return caseSensitive; 469 } 470 471 472 473 /** 474 * Specifies whether string matching should be performed in a case-sensitive 475 * manner. 476 * 477 * @param caseSensitive Indicates whether string matching should be 478 * case sensitive. 479 */ 480 public void setCaseSensitive(final boolean caseSensitive) 481 { 482 this.caseSensitive = caseSensitive; 483 } 484 485 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override() 491 public String getFilterType() 492 { 493 return FILTER_TYPE; 494 } 495 496 497 498 /** 499 * {@inheritDoc} 500 */ 501 @Override() 502 protected Set<String> getRequiredFieldNames() 503 { 504 return REQUIRED_FIELD_NAMES; 505 } 506 507 508 509 /** 510 * {@inheritDoc} 511 */ 512 @Override() 513 protected Set<String> getOptionalFieldNames() 514 { 515 return OPTIONAL_FIELD_NAMES; 516 } 517 518 519 520 /** 521 * {@inheritDoc} 522 */ 523 @Override() 524 public boolean matchesJSONObject(final JSONObject o) 525 { 526 final List<JSONValue> candidates = getValues(o, field); 527 if (candidates.isEmpty()) 528 { 529 return false; 530 } 531 532 for (final JSONValue v : candidates) 533 { 534 if (value.equals(v, false, (! caseSensitive), false)) 535 { 536 return true; 537 } 538 539 if (v instanceof JSONArray) 540 { 541 final JSONArray a = (JSONArray) v; 542 if (a.contains(value, false, (! caseSensitive), false, false)) 543 { 544 return true; 545 } 546 } 547 } 548 549 return false; 550 } 551 552 553 554 /** 555 * {@inheritDoc} 556 */ 557 @Override() 558 public JSONObject toJSONObject() 559 { 560 final LinkedHashMap<String,JSONValue> fields = new LinkedHashMap<>(4); 561 562 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE)); 563 564 if (field.size() == 1) 565 { 566 fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0))); 567 } 568 else 569 { 570 final ArrayList<JSONValue> fieldNameValues = 571 new ArrayList<>(field.size()); 572 for (final String s : field) 573 { 574 fieldNameValues.add(new JSONString(s)); 575 } 576 fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues)); 577 } 578 579 fields.put(FIELD_VALUE, value); 580 581 if (caseSensitive) 582 { 583 fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE); 584 } 585 586 return new JSONObject(fields); 587 } 588 589 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override() 595 protected EqualsJSONObjectFilter decodeFilter(final JSONObject filterObject) 596 throws JSONException 597 { 598 final List<String> fieldPath = 599 getStrings(filterObject, FIELD_FIELD_PATH, false, null); 600 601 final boolean isCaseSensitive = getBoolean(filterObject, 602 FIELD_CASE_SENSITIVE, false); 603 604 return new EqualsJSONObjectFilter(fieldPath, 605 filterObject.getField(FIELD_VALUE), isCaseSensitive); 606 } 607}