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