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