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.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.json.JSONArray;
038 import com.unboundid.util.json.JSONBoolean;
039 import com.unboundid.util.json.JSONException;
040 import com.unboundid.util.json.JSONObject;
041 import com.unboundid.util.json.JSONString;
042 import com.unboundid.util.json.JSONValue;
043
044
045
046 /**
047 * <BLOCKQUOTE>
048 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
049 * LDAP SDK for Java. It is not available for use in applications that
050 * include only the Standard Edition of the LDAP SDK, and is not supported for
051 * use in conjunction with non-UnboundID products.
052 * </BLOCKQUOTE>
053 * This class provides an implementation of a JSON object filter that can
054 * perform a logical OR across the result obtained from a number of filters.
055 * The OR filter will match an object only if at least one (and optionally,
056 * exactly one) of the filters contained in it matches that object. An OR
057 * filter with an empty set of embedded filters will never match any object.
058 * <BR><BR>
059 * The fields that are required to be included in an "OR" filter are:
060 * <UL>
061 * <LI>
062 * {@code orFilters} -- An array of JSON objects, each of which is a valid
063 * JSON object filter. At least one of these filters must match a JSON
064 * object in order for the OR filter to match. If this is an empty array,
065 * then the filter will not match any object.
066 * </LI>
067 * </UL>
068 * The fields that may optionally be included in an "OR" filter are:
069 * <UL>
070 * <LI>
071 * {@code exclusive} -- Indicates whether this should be treated as an
072 * exclusive OR. If this is present, then it must have a Boolean value of
073 * either {@code true} (to indicate that this OR filter will only match a
074 * JSON object if exactly one of the embedded filters matches that object),
075 * or {@code false} (to indicate that it is a non-exclusive OR and will
076 * match a JSON object as long as at least one of the filters matches that
077 * object). If this is not specified, then a non-exclusive OR will be
078 * performed.
079 * </LI>
080 * </UL>
081 * <H2>Examples</H2>
082 * The following is an example of an OR filter that will never match any JSON
083 * object:
084 * <PRE>
085 * { "filterType" : "or",
086 * "orFilters" : [ ] }
087 * </PRE>
088 * The above filter can be created with the code:
089 * <PRE>
090 * ORJSONObjectFilter filter = new ORJSONObjectFilter();
091 * </PRE>
092 * <BR><BR>
093 * The following is an example of an OR filter that will match any JSON object
094 * that contains either a top-level field named "homePhone" or a top-level
095 * field named "workPhone":
096 * <PRE>
097 * { "filterType" : "or",
098 * "orFilters" : [
099 * { "filterType" : "containsField",
100 * "field" : "homePhone" },
101 * { "filterType" : "containsField",
102 * "field" : "workPhone" } ] }
103 * </PRE>
104 * The above filter can be created with the code:
105 * <PRE>
106 * ORJSONObjectFilter filter = new ORJSONObjectFilter(
107 * new ContainsFieldJSONObjectFilter("homePhone"),
108 * new EqualsJSONObjectFilter("workPhone"));
109 * </PRE>
110 */
111 @Mutable()
112 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
113 public final class ORJSONObjectFilter
114 extends JSONObjectFilter
115 {
116 /**
117 * The value that should be used for the filterType element of the JSON object
118 * that represents an "OR" filter.
119 */
120 public static final String FILTER_TYPE = "or";
121
122
123
124 /**
125 * The name of the JSON field that is used to specify the set of filters to
126 * include in this OR filter.
127 */
128 public static final String FIELD_OR_FILTERS = "orFilters";
129
130
131
132 /**
133 * The name of the JSON field that is used to indicate whether this should be
134 * an exclusive OR.
135 */
136 public static final String FIELD_EXCLUSIVE = "exclusive";
137
138
139
140 /**
141 * The pre-allocated set of required field names.
142 */
143 private static final Set<String> REQUIRED_FIELD_NAMES =
144 Collections.unmodifiableSet(new HashSet<String>(
145 Collections.singletonList(FIELD_OR_FILTERS)));
146
147
148
149 /**
150 * The pre-allocated set of optional field names.
151 */
152 private static final Set<String> OPTIONAL_FIELD_NAMES =
153 Collections.unmodifiableSet(new HashSet<String>(
154 Collections.singletonList(FIELD_EXCLUSIVE)));
155
156
157
158 /**
159 * The serial version UID for this serializable class.
160 */
161 private static final long serialVersionUID = -7821418213623654386L;
162
163
164
165 // Indicates whether to process this filter as an exclusive OR.
166 private volatile boolean exclusive;
167
168 // The set of embedded filters for this OR filter.
169 private volatile List<JSONObjectFilter> orFilters;
170
171
172
173 /**
174 * Creates a new instance of this filter type with the provided information.
175 *
176 * @param orFilters The set of filters for this OR filter. At least one
177 * of these filters must match a JSON object in order for
178 * this OR filter to match that object. If this is
179 * {@code null} or empty, then this OR filter will never
180 * match any JSON object.
181 */
182 public ORJSONObjectFilter(final JSONObjectFilter... orFilters)
183 {
184 this(StaticUtils.toList(orFilters));
185 }
186
187
188
189 /**
190 * Creates a new instance of this filter type with the provided information.
191 *
192 * @param orFilters The set of filters for this OR filter. At least one
193 * of these filters must match a JSON object in order for
194 * this OR filter to match that object. If this is
195 * {@code null} or empty, then this OR filter will never
196 * match any JSON object.
197 */
198 public ORJSONObjectFilter(final Collection<JSONObjectFilter> orFilters)
199 {
200 setORFilters(orFilters);
201
202 exclusive = false;
203 }
204
205
206
207 /**
208 * Retrieves the set of filters for this OR filter. At least one of these
209 * filters must match a JSON object in order fro this OR filter to match that
210 * object.
211 *
212 * @return The set of filters for this OR filter.
213 */
214 public List<JSONObjectFilter> getORFilters()
215 {
216 return orFilters;
217 }
218
219
220
221 /**
222 * Specifies the set of filters for this OR filter. At least one of these
223 * filters must match a JSON object in order for this OR filter to match that
224 * object.
225 *
226 * @param orFilters The set of filters for this OR filter. At least one
227 * of these filters must match a JSON object in order for
228 * this OR filter to match that object. If this is
229 * {@code null} or empty, then this OR filter will never
230 * match any JSON object.
231 */
232 public void setORFilters(final JSONObjectFilter... orFilters)
233 {
234 setORFilters(StaticUtils.toList(orFilters));
235 }
236
237
238
239 /**
240 * Specifies the set of filters for this OR filter. At least one of these
241 * filters must match a JSON object in order for this OR filter to match that
242 * object.
243 *
244 * @param orFilters The set of filters for this OR filter. At least one
245 * of these filters must match a JSON object in order for
246 * this OR filter to match that object. If this is
247 * {@code null} or empty, then this OR filter will never
248 * match any JSON object.
249 */
250 public void setORFilters(final Collection<JSONObjectFilter> orFilters)
251 {
252 if ((orFilters == null) || orFilters.isEmpty())
253 {
254 this.orFilters = Collections.emptyList();
255 }
256 else
257 {
258 this.orFilters = Collections.unmodifiableList(
259 new ArrayList<JSONObjectFilter>(orFilters));
260 }
261 }
262
263
264
265 /**
266 * Indicates whether this filter should be treated as an exclusive OR, in
267 * which it will only match a JSON object if exactly one of the embedded
268 * filters matches that object.
269 *
270 * @return {@code true} if this filter should be treated as an exclusive OR
271 * and will only match a JSON object if exactly one of the embedded
272 * filters matches that object, or {@code false} if this filter will
273 * be non-exclusive and will match a JSON object as long as at least
274 * one of the embedded filters matches that object.
275 */
276 public boolean exclusive()
277 {
278 return exclusive;
279 }
280
281
282
283 /**
284 * Specifies whether this filter should be treated as an exclusive OR, in
285 * which it will only match a JSON object if exactly one of the embedded
286 * filters matches that object.
287 *
288 * @param exclusive Indicates whether this filter should be treated as an
289 * exclusive OR.
290 */
291 public void setExclusive(final boolean exclusive)
292 {
293 this.exclusive = exclusive;
294 }
295
296
297
298 /**
299 * {@inheritDoc}
300 */
301 @Override()
302 public String getFilterType()
303 {
304 return FILTER_TYPE;
305 }
306
307
308
309 /**
310 * {@inheritDoc}
311 */
312 @Override()
313 protected Set<String> getRequiredFieldNames()
314 {
315 return REQUIRED_FIELD_NAMES;
316 }
317
318
319
320 /**
321 * {@inheritDoc}
322 */
323 @Override()
324 protected Set<String> getOptionalFieldNames()
325 {
326 return OPTIONAL_FIELD_NAMES;
327 }
328
329
330
331 /**
332 * {@inheritDoc}
333 */
334 @Override()
335 public boolean matchesJSONObject(final JSONObject o)
336 {
337 boolean matchFound = false;
338 for (final JSONObjectFilter f : orFilters)
339 {
340 if (f.matchesJSONObject(o))
341 {
342 if (exclusive)
343 {
344 if (matchFound)
345 {
346 return false;
347 }
348 else
349 {
350 matchFound = true;
351 }
352 }
353 else
354 {
355 return true;
356 }
357 }
358 }
359
360 return matchFound;
361 }
362
363
364
365 /**
366 * {@inheritDoc}
367 */
368 @Override()
369 public JSONObject toJSONObject()
370 {
371 final LinkedHashMap<String,JSONValue> fields =
372 new LinkedHashMap<String,JSONValue>(3);
373
374 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
375
376 final ArrayList<JSONValue> filterValues =
377 new ArrayList<JSONValue>(orFilters.size());
378 for (final JSONObjectFilter f : orFilters)
379 {
380 filterValues.add(f.toJSONObject());
381 }
382 fields.put(FIELD_OR_FILTERS, new JSONArray(filterValues));
383
384 if (exclusive)
385 {
386 fields.put(FIELD_EXCLUSIVE, JSONBoolean.TRUE);
387 }
388
389 return new JSONObject(fields);
390 }
391
392
393
394 /**
395 * {@inheritDoc}
396 */
397 @Override()
398 protected ORJSONObjectFilter decodeFilter(final JSONObject filterObject)
399 throws JSONException
400 {
401 final ORJSONObjectFilter orFilter =
402 new ORJSONObjectFilter(getFilters(filterObject, FIELD_OR_FILTERS));
403 orFilter.exclusive = getBoolean(filterObject, FIELD_EXCLUSIVE, false);
404 return orFilter;
405 }
406 }