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