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