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 }