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