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.Debug;
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.JSONException;
041 import com.unboundid.util.json.JSONObject;
042 import com.unboundid.util.json.JSONString;
043 import com.unboundid.util.json.JSONValue;
044
045 import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*;
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 a field whose value is a JSON object
058 * that matches a provided JSON object filter, or a field whose value is an
059 * array that contains at least one JSON object that matches the provided
060 * filter.
061 * <BR><BR>
062 * The fields that are required to be included in an "object matches" filter
063 * 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 array
068 * of strings as described in the "Targeting Fields in JSON Objects" section
069 * of the class-level documentation for {@link JSONObjectFilter}. The value
070 * of the target field is expected to either be a JSON object or an array
071 * that contains one or more JSON objects.
072 * </LI>
073 * <LI>
074 * {@code filter} -- A JSON object that represents a valid JSON object
075 * filter to match against any JSON object(s) in the value of the target
076 * field. Note that field name references in this filter should be
077 * relative to the object in the value of the target field, not to the
078 * other JSON object that contains that field.
079 * </LI>
080 * </UL>
081 * <H2>Example</H2>
082 * The following is an example of an "object matches" filter that will match
083 * any JSON object with a top-level field named "contact" whose value is a JSON
084 * object (or an array containing one or more JSON objects) with a "type" field
085 * with a value of "home" and a "email" field with any value:
086 * <PRE>
087 * { "filterType" : "objectMatches",
088 * "field" : "contact",
089 * "filter" : {
090 * "filterType" : "and",
091 * "andFilters" : [
092 * { "filterType" : "equals",
093 * "field" : "type",
094 * "value" : "home" },
095 * { "filterType" : "containsField",
096 * "field" : "email" } ] } }
097 * </PRE>
098 * The above filter can be created with the code:
099 * <PRE>
100 * ObjectMatchesJSONObjectFilter filter = new ObjectMatchesJSONObjectFilter(
101 * "contact",
102 * new ANDJSONObjectFilter(
103 * new EqualsJSONObjectFilter("type", "home"),
104 * new ContainsFieldJSONObjectFilter("email")));
105 * </PRE>
106 */
107 @Mutable()
108 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
109 public final class ObjectMatchesJSONObjectFilter
110 extends JSONObjectFilter
111 {
112 /**
113 * The value that should be used for the filterType element of the JSON object
114 * that represents an "object matches" filter.
115 */
116 public static final String FILTER_TYPE = "objectMatches";
117
118
119
120 /**
121 * The name of the JSON field that is used to specify the field in the target
122 * JSON object for which to make the determination.
123 */
124 public static final String FIELD_FIELD_PATH = "field";
125
126
127
128 /**
129 * The name of the JSON field that is used to specify the filter to match
130 * against the object in the target field.
131 */
132 public static final String FIELD_FILTER = "filter";
133
134
135
136 /**
137 * The pre-allocated set of required field names.
138 */
139 private static final Set<String> REQUIRED_FIELD_NAMES =
140 Collections.unmodifiableSet(new HashSet<String>(
141 Arrays.asList(FIELD_FIELD_PATH, FIELD_FILTER)));
142
143
144
145 /**
146 * The pre-allocated set of optional field names.
147 */
148 private static final Set<String> OPTIONAL_FIELD_NAMES =
149 Collections.emptySet();
150
151
152
153 /**
154 * The serial version UID for this serializable class.
155 */
156 private static final long serialVersionUID = 7138078723547160420L;
157
158
159
160 // The filter to match against the object(s) in the target field.
161 private volatile JSONObjectFilter filter;
162
163 // The field path specifier for the target field.
164 private volatile List<String> field;
165
166
167
168 /**
169 * Creates an instance of this filter type that can only be used for decoding
170 * JSON objects as "object matches" filters. It cannot be used as a regular
171 * "object matches" filter.
172 */
173 ObjectMatchesJSONObjectFilter()
174 {
175 field = null;
176 filter = null;
177 }
178
179
180
181 /**
182 * Creates a new instance of this filter type with the provided information.
183 *
184 * @param field The name of the top-level field to target with this filter.
185 * It must not be {@code null} . See the class-level
186 * documentation for the {@link JSONObjectFilter} class for
187 * information about field path specifiers.
188 * @param filter The filter that will be matched against JSON objects
189 * contained in the specified field.
190 */
191 public ObjectMatchesJSONObjectFilter(final String field,
192 final JSONObjectFilter filter)
193 {
194 this(Collections.singletonList(field), filter);
195 }
196
197
198
199 /**
200 * Creates a new instance of this filter type with the provided information.
201 *
202 * @param field The field path specifier for this filter. It must not be
203 * {@code null} or empty. See the class-level documentation
204 * for the {@link JSONObjectFilter} class for information
205 * about field path specifiers.
206 * @param filter The filter that will be matched against JSON objects
207 * contained in the specified field.
208 */
209 public ObjectMatchesJSONObjectFilter(final List<String> field,
210 final JSONObjectFilter filter)
211 {
212 Validator.ensureNotNull(field);
213 Validator.ensureFalse(field.isEmpty());
214
215 Validator.ensureNotNull(filter);
216
217 this.field = Collections.unmodifiableList(new ArrayList<String>(field));
218 this.filter = filter;
219 }
220
221
222
223 /**
224 * Retrieves the field path specifier for this filter.
225 *
226 * @return The field path specifier for this filter.
227 */
228 public List<String> getField()
229 {
230 return field;
231 }
232
233
234
235 /**
236 * Sets the field path specifier for this filter.
237 *
238 * @param field The field path specifier for this filter. It must not be
239 * {@code null} or empty. See the class-level documentation
240 * for the {@link JSONObjectFilter} class for information about
241 * field path specifiers.
242 */
243 public void setField(final String... field)
244 {
245 setField(StaticUtils.toList(field));
246 }
247
248
249
250 /**
251 * Sets the field path specifier for this filter.
252 *
253 * @param field The field path specifier for this filter. It must not be
254 * {@code null} or empty. See the class-level documentation
255 * for the {@link JSONObjectFilter} class for information about
256 * field path specifiers.
257 */
258 public void setField(final List<String> field)
259 {
260 Validator.ensureNotNull(field);
261 Validator.ensureFalse(field.isEmpty());
262
263 this.field = Collections.unmodifiableList(new ArrayList<String>(field));
264 }
265
266
267
268 /**
269 * Retrieves the filter that will be matched against any JSON objects
270 * contained in the value of the specified field.
271 *
272 * @return The filter that will be matched against any JSON objects contained
273 * in the value of the specified field.
274 */
275 public JSONObjectFilter getFilter()
276 {
277 return filter;
278 }
279
280
281
282 /**
283 * Specifies the filter that will be matched against any JSON objects
284 * contained in the value of the specified field.
285 *
286 * @param filter The filter that will be matched against any JSON objects
287 * contained in the value of the specified field. It must
288 * not be {@code null}.
289 */
290 public void setFilter(final JSONObjectFilter filter)
291 {
292 Validator.ensureNotNull(filter);
293
294 this.filter = filter;
295 }
296
297
298
299 /**
300 * {@inheritDoc}
301 */
302 @Override()
303 public String getFilterType()
304 {
305 return FILTER_TYPE;
306 }
307
308
309
310 /**
311 * {@inheritDoc}
312 */
313 @Override()
314 protected Set<String> getRequiredFieldNames()
315 {
316 return REQUIRED_FIELD_NAMES;
317 }
318
319
320
321 /**
322 * {@inheritDoc}
323 */
324 @Override()
325 protected Set<String> getOptionalFieldNames()
326 {
327 return OPTIONAL_FIELD_NAMES;
328 }
329
330
331
332 /**
333 * {@inheritDoc}
334 */
335 @Override()
336 public boolean matchesJSONObject(final JSONObject o)
337 {
338 final List<JSONValue> candidates = getValues(o, field);
339 if (candidates.isEmpty())
340 {
341 return false;
342 }
343
344 for (final JSONValue v : candidates)
345 {
346 if (v instanceof JSONObject)
347 {
348 if (filter.matchesJSONObject((JSONObject) v))
349 {
350 return true;
351 }
352 }
353 else if (v instanceof JSONArray)
354 {
355 for (final JSONValue arrayValue : ((JSONArray) v).getValues())
356 {
357 if ((arrayValue instanceof JSONObject) &&
358 filter.matchesJSONObject((JSONObject) arrayValue))
359 {
360 return true;
361 }
362 }
363 }
364 }
365
366 return false;
367 }
368
369
370
371 /**
372 * {@inheritDoc}
373 */
374 @Override()
375 public JSONObject toJSONObject()
376 {
377 final LinkedHashMap<String,JSONValue> fields =
378 new LinkedHashMap<String,JSONValue>(3);
379
380 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
381
382 if (field.size() == 1)
383 {
384 fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0)));
385 }
386 else
387 {
388 final ArrayList<JSONValue> fieldNameValues =
389 new ArrayList<JSONValue>(field.size());
390 for (final String s : field)
391 {
392 fieldNameValues.add(new JSONString(s));
393 }
394 fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues));
395 }
396
397 fields.put(FIELD_FILTER, filter.toJSONObject());
398
399 return new JSONObject(fields);
400 }
401
402
403
404 /**
405 * {@inheritDoc}
406 */
407 @Override()
408 protected ObjectMatchesJSONObjectFilter decodeFilter(
409 final JSONObject filterObject)
410 throws JSONException
411 {
412 final List<String> fieldPath =
413 getStrings(filterObject, FIELD_FIELD_PATH, false, null);
414
415 final JSONValue v = filterObject.getField(FIELD_FILTER);
416 if (v == null)
417 {
418 throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
419 String.valueOf(filterObject), FILTER_TYPE, FIELD_FILTER));
420 }
421
422 if (! (v instanceof JSONObject))
423 {
424 throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_OBJECT.get(
425 String.valueOf(filterObject), FILTER_TYPE, FIELD_FILTER));
426 }
427
428 try
429 {
430 return new ObjectMatchesJSONObjectFilter(fieldPath,
431 JSONObjectFilter.decode((JSONObject) v));
432 }
433 catch (final JSONException e)
434 {
435 Debug.debugException(e);
436 throw new JSONException(
437 ERR_OBJECT_FILTER_VALUE_NOT_FILTER.get(String.valueOf(filterObject),
438 FILTER_TYPE, FIELD_FILTER, e.getMessage()),
439 e);
440 }
441 }
442 }