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 }