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