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 }