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