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.math.BigDecimal; 026 import java.util.ArrayList; 027 import java.util.Arrays; 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.JSONNumber; 043 import com.unboundid.util.json.JSONObject; 044 import com.unboundid.util.json.JSONString; 045 import com.unboundid.util.json.JSONValue; 046 047 048 049 /** 050 * <BLOCKQUOTE> 051 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID 052 * LDAP SDK for Java. It is not available for use in applications that 053 * include only the Standard Edition of the LDAP SDK, and is not supported for 054 * use in conjunction with non-UnboundID products. 055 * </BLOCKQUOTE> 056 * This class provides an implementation of a JSON object filter that can be 057 * used to identify JSON objects that have at least one value for a specified 058 * field that is greater than a given value. 059 * <BR><BR> 060 * The fields that are required to be included in a "greater than" filter are: 061 * <UL> 062 * <LI> 063 * {@code field} -- A field path specifier for the JSON field for which to 064 * make the determination. This may be either a single string or an array 065 * of strings as described in the "Targeting Fields in JSON Objects" section 066 * of the class-level documentation for {@link JSONObjectFilter}. 067 * </LI> 068 * <LI> 069 * {@code value} -- The value to use in the matching. It must be either a 070 * string (which will be compared against other strings using lexicographic 071 * comparison) or a number. 072 * </LI> 073 * </UL> 074 * The fields that may optionally be included in a "greater than" filter are: 075 * <UL> 076 * <LI> 077 * {@code allowEquals} -- Indicates whether to match JSON objects that have 078 * a value for the specified field that matches the provided value. If 079 * present, this field must have a Boolean value of either {@code true} (to 080 * indicate that it should be a "greater-than or equal to" filter) or 081 * {@code false} (to indicate that it should be a strict "greater-than" 082 * filter). If this is not specified, then the default behavior will be to 083 * perform a strict "greater-than" evaluation. 084 * </LI> 085 * <LI> 086 * {@code matchAllElements} -- Indicates whether all elements of an array 087 * must be greater than (or possibly equal to) the specified value. If 088 * present, this field must have a Boolean value of {@code true} (to 089 * indicate that all elements of the array must match the criteria for this 090 * filter) or {@code false} (to indicate that at least one element of the 091 * array must match the criteria for this filter). If this is not 092 * specified, then the default behavior will be to require only at least 093 * one matching element. This field will be ignored for JSON objects in 094 * which the specified field has a value that is not an array. 095 * </LI> 096 * <LI> 097 * {@code caseSensitive} -- Indicates whether string values should be 098 * treated in a case-sensitive manner. If present, this field must have a 099 * Boolean value of either {@code true} or {@code false}. If it is not 100 * provided, then a default value of {@code false} will be assumed so that 101 * strings are treated in a case-insensitive manner. 102 * </LI> 103 * </UL> 104 * <H2>Example</H2> 105 * The following is an example of a "greater than" filter that will match any 106 * JSON object with a top-level field named "salary" with a value that is 107 * greater than or equal to 50000: 108 * <PRE> 109 * { "filterType" : "greaterThan", 110 * "field" : "salary", 111 * "value" : 50000, 112 * "allowEquals" : true } 113 * </PRE> 114 * The above filter can be created with the code: 115 * <PRE> 116 * GreaterThanJSONObjectFilter filter = 117 * new GreaterThanJSONObjectFilter("salary", 50000); 118 * filter.setAllowEquals(true); 119 * </PRE> 120 */ 121 @Mutable() 122 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 123 public final class GreaterThanJSONObjectFilter 124 extends JSONObjectFilter 125 { 126 /** 127 * The value that should be used for the filterType element of the JSON object 128 * that represents a "greater than" filter. 129 */ 130 public static final String FILTER_TYPE = "greaterThan"; 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 to match JSON 152 * objects with a value that is considered equal to the provided value. 153 */ 154 public static final String FIELD_ALLOW_EQUALS = "allowEquals"; 155 156 157 158 /** 159 * The name of the JSON field that is used to indicate whether to match all 160 * elements of an array rather than just one or more. 161 */ 162 public static final String FIELD_MATCH_ALL_ELEMENTS = "matchAllElements"; 163 164 165 166 /** 167 * The name of the JSON field that is used to indicate whether string matching 168 * should be case-sensitive. 169 */ 170 public static final String FIELD_CASE_SENSITIVE = "caseSensitive"; 171 172 173 174 /** 175 * The pre-allocated set of required field names. 176 */ 177 private static final Set<String> REQUIRED_FIELD_NAMES = 178 Collections.unmodifiableSet(new HashSet<String>( 179 Arrays.asList(FIELD_FIELD_PATH, FIELD_VALUE))); 180 181 182 183 /** 184 * The pre-allocated set of optional field names. 185 */ 186 private static final Set<String> OPTIONAL_FIELD_NAMES = 187 Collections.unmodifiableSet(new HashSet<String>( 188 Arrays.asList(FIELD_ALLOW_EQUALS, FIELD_MATCH_ALL_ELEMENTS, 189 FIELD_CASE_SENSITIVE))); 190 191 192 193 /** 194 * The serial version UID for this serializable class. 195 */ 196 private static final long serialVersionUID = -8397741931424599570L; 197 198 199 200 // Indicates whether to match equivalent values in addition to those that are 201 // strictly greater than the target value. 202 private volatile boolean allowEquals; 203 204 // Indicates whether string matching should be case-sensitive. 205 private volatile boolean caseSensitive; 206 207 // Indicates whether to match all elements of an array rather than just one or 208 // more. 209 private volatile boolean matchAllElements; 210 211 // The expected value for the target field. 212 private volatile JSONValue value; 213 214 // The field path specifier for the target field. 215 private volatile List<String> field; 216 217 218 219 /** 220 * Creates an instance of this filter type that can only be used for decoding 221 * JSON objects as "greater than" filters. It cannot be used as a regular 222 * "greater than" filter. 223 */ 224 GreaterThanJSONObjectFilter() 225 { 226 field = null; 227 value = null; 228 allowEquals = false; 229 matchAllElements = false; 230 caseSensitive = false; 231 } 232 233 234 235 /** 236 * Creates a new instance of this filter type with the provided information. 237 * 238 * @param field The field path specifier for the target field. 239 * @param value The expected value for the target field. 240 * @param allowEquals Indicates whether to match values that are equal 241 * to the provided value in addition to those that 242 * are strictly greater than that value. 243 * @param matchAllElements Indicates whether, if the value of the target 244 * field is an array, all elements of that array 245 * will be required to match the criteria of this 246 * filter. 247 * @param caseSensitive Indicates whether string matching should be 248 * case sensitive. 249 */ 250 private GreaterThanJSONObjectFilter(final List<String> field, 251 final JSONValue value, 252 final boolean allowEquals, 253 final boolean matchAllElements, 254 final boolean caseSensitive) 255 { 256 this.field = field; 257 this.value = value; 258 this.allowEquals = allowEquals; 259 this.matchAllElements = matchAllElements; 260 this.caseSensitive = caseSensitive; 261 } 262 263 264 265 /** 266 * Creates a new instance of this filter type with the provided information. 267 * 268 * @param field The name of the top-level field to target with this filter. 269 * It must not be {@code null} . See the class-level 270 * documentation for the {@link JSONObjectFilter} class for 271 * information about field path specifiers. 272 * @param value The target value for this filter. 273 */ 274 public GreaterThanJSONObjectFilter(final String field, final long value) 275 { 276 this(Collections.singletonList(field), new JSONNumber(value)); 277 } 278 279 280 281 /** 282 * Creates a new instance of this filter type with the provided information. 283 * 284 * @param field The name of the top-level field to target with this filter. 285 * It must not be {@code null} . See the class-level 286 * documentation for the {@link JSONObjectFilter} class for 287 * information about field path specifiers. 288 * @param value The target value for this filter. 289 */ 290 public GreaterThanJSONObjectFilter(final String field, final double value) 291 { 292 this(Collections.singletonList(field), new JSONNumber(value)); 293 } 294 295 296 297 /** 298 * Creates a new instance of this filter type with the provided information. 299 * 300 * @param field The name of the top-level field to target with this filter. 301 * It must not be {@code null} . See the class-level 302 * documentation for the {@link JSONObjectFilter} class for 303 * information about field path specifiers. 304 * @param value The target value for this filter. It must not be 305 * {@code null}. 306 */ 307 public GreaterThanJSONObjectFilter(final String field, final String value) 308 { 309 this(Collections.singletonList(field), new JSONString(value)); 310 } 311 312 313 314 /** 315 * Creates a new instance of this filter type with the provided information. 316 * 317 * @param field The name of the top-level field to target with this filter. 318 * It must not be {@code null} . See the class-level 319 * documentation for the {@link JSONObjectFilter} class for 320 * information about field path specifiers. 321 * @param value The target value for this filter. It must not be 322 * {@code null}, and it must be either a {@link JSONNumber} or 323 * a {@link JSONString}. 324 */ 325 public GreaterThanJSONObjectFilter(final String field, 326 final JSONValue value) 327 { 328 this(Collections.singletonList(field), value); 329 } 330 331 332 333 /** 334 * Creates a new instance of this filter type with the provided information. 335 * 336 * @param field The field path specifier for this filter. It must not be 337 * {@code null} or empty. See the class-level documentation 338 * for the {@link JSONObjectFilter} class for information about 339 * field path specifiers. 340 * @param value The target value for this filter. It must not be 341 * {@code null}, and it must be either a {@link JSONNumber} or 342 * a {@link JSONString}. 343 */ 344 public GreaterThanJSONObjectFilter(final List<String> field, 345 final JSONValue value) 346 { 347 Validator.ensureNotNull(field); 348 Validator.ensureFalse(field.isEmpty()); 349 350 Validator.ensureNotNull(value); 351 Validator.ensureTrue((value instanceof JSONNumber) || 352 (value instanceof JSONString)); 353 354 this.field = Collections.unmodifiableList(new ArrayList<String>(field)); 355 this.value = value; 356 357 allowEquals = false; 358 matchAllElements = false; 359 caseSensitive = false; 360 } 361 362 363 364 /** 365 * Retrieves the field path specifier for this filter. 366 * 367 * @return The field path specifier for this filter. 368 */ 369 public List<String> getField() 370 { 371 return field; 372 } 373 374 375 376 /** 377 * Sets the field path specifier for this filter. 378 * 379 * @param field The field path specifier for this filter. It must not be 380 * {@code null} or empty. See the class-level documentation 381 * for the {@link JSONObjectFilter} class for information about 382 * field path specifiers. 383 */ 384 public void setField(final String... field) 385 { 386 setField(StaticUtils.toList(field)); 387 } 388 389 390 391 /** 392 * Sets the field path specifier for this filter. 393 * 394 * @param field The field path specifier for this filter. It must not be 395 * {@code null} or empty. See the class-level documentation 396 * for the {@link JSONObjectFilter} class for information about 397 * field path specifiers. 398 */ 399 public void setField(final List<String> field) 400 { 401 Validator.ensureNotNull(field); 402 Validator.ensureFalse(field.isEmpty()); 403 404 this.field = Collections.unmodifiableList(new ArrayList<String>(field)); 405 } 406 407 408 409 /** 410 * Retrieves the target value for this filter. 411 * 412 * @return The target value for this filter. 413 */ 414 public JSONValue getValue() 415 { 416 return value; 417 } 418 419 420 421 /** 422 * Specifies the target value for this filter. 423 * 424 * @param value The target value for this filter. 425 */ 426 public void setValue(final long value) 427 { 428 setValue(new JSONNumber(value)); 429 } 430 431 432 433 /** 434 * Specifies the target value for this filter. 435 * 436 * @param value The target value for this filter. 437 */ 438 public void setValue(final double value) 439 { 440 setValue(new JSONNumber(value)); 441 } 442 443 444 445 /** 446 * Specifies the target value for this filter. 447 * 448 * @param value The target value for this filter. It must not be 449 * {@code null}. 450 */ 451 public void setValue(final String value) 452 { 453 Validator.ensureNotNull(value); 454 455 setValue(new JSONString(value)); 456 } 457 458 459 460 /** 461 * Specifies the target value for this filter. 462 * 463 * @param value The target value for this filter. It must not be 464 * {@code null}, and it must be either a {@link JSONNumber} or 465 * a {@link JSONString}. 466 */ 467 public void setValue(final JSONValue value) 468 { 469 Validator.ensureNotNull(value); 470 Validator.ensureTrue((value instanceof JSONNumber) || 471 (value instanceof JSONString)); 472 473 this.value = value; 474 } 475 476 477 478 /** 479 * Indicates whether this filter will match values that are considered equal 480 * to the provided value in addition to those that are strictly greater than 481 * that value. 482 * 483 * @return {@code true} if this filter should behave like a "greater than or 484 * equal to" filter, or {@code false} if it should behave strictly 485 * like a "greater than" filter. 486 */ 487 public boolean allowEquals() 488 { 489 return allowEquals; 490 } 491 492 493 494 /** 495 * Specifies whether this filter should match values that are considered equal 496 * to the provided value in addition to those that are strictly greater than 497 * that value. 498 * 499 * @param allowEquals Indicates whether this filter should match values that 500 * are considered equal to the provided value in addition 501 * to those that are strictly greater than this value. 502 */ 503 public void setAllowEquals(final boolean allowEquals) 504 { 505 this.allowEquals = allowEquals; 506 } 507 508 509 510 /** 511 * Indicates whether, if the specified field has a value that is an array, to 512 * require all elements of that array to match the criteria for this filter 513 * rather than merely requiring at least one value to match. 514 * 515 * @return {@code true} if the criteria contained in this filter will be 516 * required to match all elements of an array, or {@code false} if 517 * merely one or more values will be required to match. 518 */ 519 public boolean matchAllElements() 520 { 521 return matchAllElements; 522 } 523 524 525 526 /** 527 * Specifies whether, if the value of the target field is an array, all 528 * elements of that array will be required to match the criteria of this 529 * filter. This will be ignored if the value of the target field is not an 530 * array. 531 * 532 * @param matchAllElements {@code true} to indicate that all elements of an 533 * array will be required to match the criteria of 534 * this filter, or {@code false} to indicate that 535 * merely one or more values will be required to 536 * match. 537 */ 538 public void setMatchAllElements(final boolean matchAllElements) 539 { 540 this.matchAllElements = matchAllElements; 541 } 542 543 544 545 /** 546 * Indicates whether string matching should be performed in a case-sensitive 547 * manner. 548 * 549 * @return {@code true} if string matching should be case sensitive, or 550 * {@code false} if not. 551 */ 552 public boolean caseSensitive() 553 { 554 return caseSensitive; 555 } 556 557 558 559 /** 560 * Specifies whether string matching should be performed in a case-sensitive 561 * manner. 562 * 563 * @param caseSensitive Indicates whether string matching should be 564 * case sensitive. 565 */ 566 public void setCaseSensitive(final boolean caseSensitive) 567 { 568 this.caseSensitive = caseSensitive; 569 } 570 571 572 573 /** 574 * {@inheritDoc} 575 */ 576 @Override() 577 public String getFilterType() 578 { 579 return FILTER_TYPE; 580 } 581 582 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override() 588 protected Set<String> getRequiredFieldNames() 589 { 590 return REQUIRED_FIELD_NAMES; 591 } 592 593 594 595 /** 596 * {@inheritDoc} 597 */ 598 @Override() 599 protected Set<String> getOptionalFieldNames() 600 { 601 return OPTIONAL_FIELD_NAMES; 602 } 603 604 605 606 /** 607 * {@inheritDoc} 608 */ 609 @Override() 610 public boolean matchesJSONObject(final JSONObject o) 611 { 612 final List<JSONValue> candidates = getValues(o, field); 613 if (candidates.isEmpty()) 614 { 615 return false; 616 } 617 618 for (final JSONValue v : candidates) 619 { 620 if (v instanceof JSONArray) 621 { 622 boolean matchOne = false; 623 boolean matchAll = true; 624 for (final JSONValue arrayValue : ((JSONArray) v).getValues()) 625 { 626 if (matches(arrayValue)) 627 { 628 if (! matchAllElements) 629 { 630 return true; 631 } 632 matchOne = true; 633 } 634 else 635 { 636 matchAll = false; 637 if (matchAllElements) 638 { 639 break; 640 } 641 } 642 } 643 644 if (matchAllElements && matchOne && matchAll) 645 { 646 return true; 647 } 648 } 649 else if (matches(v)) 650 { 651 return true; 652 } 653 } 654 655 return false; 656 } 657 658 659 660 /** 661 * Indicates whether the provided value matches the criteria of this filter. 662 * 663 * @param v The value for which to make the determination. 664 * 665 * @return {@code true} if the provided value matches the criteria of this 666 * filter, or {@code false} if not. 667 */ 668 private boolean matches(final JSONValue v) 669 { 670 if ((v instanceof JSONNumber) && (value instanceof JSONNumber)) 671 { 672 final BigDecimal targetValue = ((JSONNumber) value).getValue(); 673 final BigDecimal objectValue = ((JSONNumber) v).getValue(); 674 if (allowEquals) 675 { 676 return (objectValue.compareTo(targetValue) >= 0); 677 } 678 else 679 { 680 return (objectValue.compareTo(targetValue) > 0); 681 } 682 } 683 else if ((v instanceof JSONString) && (value instanceof JSONString)) 684 { 685 final String targetValue = ((JSONString) value).stringValue(); 686 final String objectValue = ((JSONString) v).stringValue(); 687 if (allowEquals) 688 { 689 if (caseSensitive) 690 { 691 return (objectValue.compareTo(targetValue) >= 0); 692 } 693 else 694 { 695 return (objectValue.compareToIgnoreCase(targetValue) >= 0); 696 } 697 } 698 else 699 { 700 if (caseSensitive) 701 { 702 return (objectValue.compareTo(targetValue) > 0); 703 } 704 else 705 { 706 return (objectValue.compareToIgnoreCase(targetValue) > 0); 707 } 708 } 709 } 710 else 711 { 712 return false; 713 } 714 } 715 716 717 718 /** 719 * {@inheritDoc} 720 */ 721 @Override() 722 public JSONObject toJSONObject() 723 { 724 final LinkedHashMap<String,JSONValue> fields = 725 new LinkedHashMap<String,JSONValue>(6); 726 727 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE)); 728 729 if (field.size() == 1) 730 { 731 fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0))); 732 } 733 else 734 { 735 final ArrayList<JSONValue> fieldNameValues = 736 new ArrayList<JSONValue>(field.size()); 737 for (final String s : field) 738 { 739 fieldNameValues.add(new JSONString(s)); 740 } 741 fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues)); 742 } 743 744 fields.put(FIELD_VALUE, value); 745 746 if (allowEquals) 747 { 748 fields.put(FIELD_ALLOW_EQUALS, JSONBoolean.TRUE); 749 } 750 751 if (matchAllElements) 752 { 753 fields.put(FIELD_MATCH_ALL_ELEMENTS, JSONBoolean.TRUE); 754 } 755 756 if (caseSensitive) 757 { 758 fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE); 759 } 760 761 return new JSONObject(fields); 762 } 763 764 765 766 /** 767 * {@inheritDoc} 768 */ 769 @Override() 770 protected GreaterThanJSONObjectFilter decodeFilter( 771 final JSONObject filterObject) 772 throws JSONException 773 { 774 final List<String> fieldPath = 775 getStrings(filterObject, FIELD_FIELD_PATH, false, null); 776 777 final boolean isAllowEquals = getBoolean(filterObject, 778 FIELD_ALLOW_EQUALS, false); 779 780 final boolean isMatchAllElements = getBoolean(filterObject, 781 FIELD_MATCH_ALL_ELEMENTS, false); 782 783 final boolean isCaseSensitive = getBoolean(filterObject, 784 FIELD_CASE_SENSITIVE, false); 785 786 return new GreaterThanJSONObjectFilter(fieldPath, 787 filterObject.getField(FIELD_VALUE), isAllowEquals, isMatchAllElements, 788 isCaseSensitive); 789 } 790 }