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