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.Arrays; 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.Debug; 034 import com.unboundid.util.Mutable; 035 import com.unboundid.util.StaticUtils; 036 import com.unboundid.util.ThreadSafety; 037 import com.unboundid.util.ThreadSafetyLevel; 038 import com.unboundid.util.Validator; 039 import com.unboundid.util.json.JSONArray; 040 import com.unboundid.util.json.JSONException; 041 import com.unboundid.util.json.JSONObject; 042 import com.unboundid.util.json.JSONString; 043 import com.unboundid.util.json.JSONValue; 044 045 import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*; 046 047 048 049 /** 050 * <BLOCKQUOTE> 051 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID 052 * LDAP SDK for Java. It is not available for use in applications that 053 * include only the Standard Edition of the LDAP SDK, and is not supported for 054 * use in conjunction with non-UnboundID products. 055 * </BLOCKQUOTE> 056 * This class provides an implementation of a JSON object filter that can be 057 * used to identify JSON objects that have a field whose value is a JSON object 058 * that matches a provided JSON object filter, or a field whose value is an 059 * array that contains at least one JSON object that matches the provided 060 * filter. 061 * <BR><BR> 062 * The fields that are required to be included in an "object matches" filter 063 * are: 064 * <UL> 065 * <LI> 066 * {@code field} -- A field path specifier for the JSON field for which to 067 * make the determination. This may be either a single string or an array 068 * of strings as described in the "Targeting Fields in JSON Objects" section 069 * of the class-level documentation for {@link JSONObjectFilter}. The value 070 * of the target field is expected to either be a JSON object or an array 071 * that contains one or more JSON objects. 072 * </LI> 073 * <LI> 074 * {@code filter} -- A JSON object that represents a valid JSON object 075 * filter to match against any JSON object(s) in the value of the target 076 * field. Note that field name references in this filter should be 077 * relative to the object in the value of the target field, not to the 078 * other JSON object that contains that field. 079 * </LI> 080 * </UL> 081 * <H2>Example</H2> 082 * The following is an example of an "object matches" filter that will match 083 * any JSON object with a top-level field named "contact" whose value is a JSON 084 * object (or an array containing one or more JSON objects) with a "type" field 085 * with a value of "home" and a "email" field with any value: 086 * <PRE> 087 * { "filterType" : "objectMatches", 088 * "field" : "contact", 089 * "filter" : { 090 * "filterType" : "and", 091 * "andFilters" : [ 092 * { "filterType" : "equals", 093 * "field" : "type", 094 * "value" : "home" }, 095 * { "filterType" : "containsField", 096 * "field" : "email" } ] } } 097 * </PRE> 098 * The above filter can be created with the code: 099 * <PRE> 100 * ObjectMatchesJSONObjectFilter filter = new ObjectMatchesJSONObjectFilter( 101 * "contact", 102 * new ANDJSONObjectFilter( 103 * new EqualsJSONObjectFilter("type", "home"), 104 * new ContainsFieldJSONObjectFilter("email"))); 105 * </PRE> 106 */ 107 @Mutable() 108 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 109 public final class ObjectMatchesJSONObjectFilter 110 extends JSONObjectFilter 111 { 112 /** 113 * The value that should be used for the filterType element of the JSON object 114 * that represents an "object matches" filter. 115 */ 116 public static final String FILTER_TYPE = "objectMatches"; 117 118 119 120 /** 121 * The name of the JSON field that is used to specify the field in the target 122 * JSON object for which to make the determination. 123 */ 124 public static final String FIELD_FIELD_PATH = "field"; 125 126 127 128 /** 129 * The name of the JSON field that is used to specify the filter to match 130 * against the object in the target field. 131 */ 132 public static final String FIELD_FILTER = "filter"; 133 134 135 136 /** 137 * The pre-allocated set of required field names. 138 */ 139 private static final Set<String> REQUIRED_FIELD_NAMES = 140 Collections.unmodifiableSet(new HashSet<String>( 141 Arrays.asList(FIELD_FIELD_PATH, FIELD_FILTER))); 142 143 144 145 /** 146 * The pre-allocated set of optional field names. 147 */ 148 private static final Set<String> OPTIONAL_FIELD_NAMES = 149 Collections.emptySet(); 150 151 152 153 /** 154 * The serial version UID for this serializable class. 155 */ 156 private static final long serialVersionUID = 7138078723547160420L; 157 158 159 160 // The filter to match against the object(s) in the target field. 161 private volatile JSONObjectFilter filter; 162 163 // The field path specifier for the target field. 164 private volatile List<String> field; 165 166 167 168 /** 169 * Creates an instance of this filter type that can only be used for decoding 170 * JSON objects as "object matches" filters. It cannot be used as a regular 171 * "object matches" filter. 172 */ 173 ObjectMatchesJSONObjectFilter() 174 { 175 field = null; 176 filter = null; 177 } 178 179 180 181 /** 182 * Creates a new instance of this filter type with the provided information. 183 * 184 * @param field The name of the top-level field to target with this filter. 185 * It must not be {@code null} . See the class-level 186 * documentation for the {@link JSONObjectFilter} class for 187 * information about field path specifiers. 188 * @param filter The filter that will be matched against JSON objects 189 * contained in the specified field. 190 */ 191 public ObjectMatchesJSONObjectFilter(final String field, 192 final JSONObjectFilter filter) 193 { 194 this(Collections.singletonList(field), filter); 195 } 196 197 198 199 /** 200 * Creates a new instance of this filter type with the provided information. 201 * 202 * @param field The field path specifier for this filter. It must not be 203 * {@code null} or empty. See the class-level documentation 204 * for the {@link JSONObjectFilter} class for information 205 * about field path specifiers. 206 * @param filter The filter that will be matched against JSON objects 207 * contained in the specified field. 208 */ 209 public ObjectMatchesJSONObjectFilter(final List<String> field, 210 final JSONObjectFilter filter) 211 { 212 Validator.ensureNotNull(field); 213 Validator.ensureFalse(field.isEmpty()); 214 215 Validator.ensureNotNull(filter); 216 217 this.field = Collections.unmodifiableList(new ArrayList<String>(field)); 218 this.filter = filter; 219 } 220 221 222 223 /** 224 * Retrieves the field path specifier for this filter. 225 * 226 * @return The field path specifier for this filter. 227 */ 228 public List<String> getField() 229 { 230 return field; 231 } 232 233 234 235 /** 236 * Sets the field path specifier for this filter. 237 * 238 * @param field The field path specifier for this filter. It must not be 239 * {@code null} or empty. See the class-level documentation 240 * for the {@link JSONObjectFilter} class for information about 241 * field path specifiers. 242 */ 243 public void setField(final String... field) 244 { 245 setField(StaticUtils.toList(field)); 246 } 247 248 249 250 /** 251 * Sets the field path specifier for this filter. 252 * 253 * @param field The field path specifier for this filter. It must not be 254 * {@code null} or empty. See the class-level documentation 255 * for the {@link JSONObjectFilter} class for information about 256 * field path specifiers. 257 */ 258 public void setField(final List<String> field) 259 { 260 Validator.ensureNotNull(field); 261 Validator.ensureFalse(field.isEmpty()); 262 263 this.field = Collections.unmodifiableList(new ArrayList<String>(field)); 264 } 265 266 267 268 /** 269 * Retrieves the filter that will be matched against any JSON objects 270 * contained in the value of the specified field. 271 * 272 * @return The filter that will be matched against any JSON objects contained 273 * in the value of the specified field. 274 */ 275 public JSONObjectFilter getFilter() 276 { 277 return filter; 278 } 279 280 281 282 /** 283 * Specifies the filter that will be matched against any JSON objects 284 * contained in the value of the specified field. 285 * 286 * @param filter The filter that will be matched against any JSON objects 287 * contained in the value of the specified field. It must 288 * not be {@code null}. 289 */ 290 public void setFilter(final JSONObjectFilter filter) 291 { 292 Validator.ensureNotNull(filter); 293 294 this.filter = filter; 295 } 296 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override() 303 public String getFilterType() 304 { 305 return FILTER_TYPE; 306 } 307 308 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override() 314 protected Set<String> getRequiredFieldNames() 315 { 316 return REQUIRED_FIELD_NAMES; 317 } 318 319 320 321 /** 322 * {@inheritDoc} 323 */ 324 @Override() 325 protected Set<String> getOptionalFieldNames() 326 { 327 return OPTIONAL_FIELD_NAMES; 328 } 329 330 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override() 336 public boolean matchesJSONObject(final JSONObject o) 337 { 338 final List<JSONValue> candidates = getValues(o, field); 339 if (candidates.isEmpty()) 340 { 341 return false; 342 } 343 344 for (final JSONValue v : candidates) 345 { 346 if (v instanceof JSONObject) 347 { 348 if (filter.matchesJSONObject((JSONObject) v)) 349 { 350 return true; 351 } 352 } 353 else if (v instanceof JSONArray) 354 { 355 for (final JSONValue arrayValue : ((JSONArray) v).getValues()) 356 { 357 if ((arrayValue instanceof JSONObject) && 358 filter.matchesJSONObject((JSONObject) arrayValue)) 359 { 360 return true; 361 } 362 } 363 } 364 } 365 366 return false; 367 } 368 369 370 371 /** 372 * {@inheritDoc} 373 */ 374 @Override() 375 public JSONObject toJSONObject() 376 { 377 final LinkedHashMap<String,JSONValue> fields = 378 new LinkedHashMap<String,JSONValue>(3); 379 380 fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE)); 381 382 if (field.size() == 1) 383 { 384 fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0))); 385 } 386 else 387 { 388 final ArrayList<JSONValue> fieldNameValues = 389 new ArrayList<JSONValue>(field.size()); 390 for (final String s : field) 391 { 392 fieldNameValues.add(new JSONString(s)); 393 } 394 fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues)); 395 } 396 397 fields.put(FIELD_FILTER, filter.toJSONObject()); 398 399 return new JSONObject(fields); 400 } 401 402 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override() 408 protected ObjectMatchesJSONObjectFilter decodeFilter( 409 final JSONObject filterObject) 410 throws JSONException 411 { 412 final List<String> fieldPath = 413 getStrings(filterObject, FIELD_FIELD_PATH, false, null); 414 415 final JSONValue v = filterObject.getField(FIELD_FILTER); 416 if (v == null) 417 { 418 throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get( 419 String.valueOf(filterObject), FILTER_TYPE, FIELD_FILTER)); 420 } 421 422 if (! (v instanceof JSONObject)) 423 { 424 throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_OBJECT.get( 425 String.valueOf(filterObject), FILTER_TYPE, FIELD_FILTER)); 426 } 427 428 try 429 { 430 return new ObjectMatchesJSONObjectFilter(fieldPath, 431 JSONObjectFilter.decode((JSONObject) v)); 432 } 433 catch (final JSONException e) 434 { 435 Debug.debugException(e); 436 throw new JSONException( 437 ERR_OBJECT_FILTER_VALUE_NOT_FILTER.get(String.valueOf(filterObject), 438 FILTER_TYPE, FIELD_FILTER, e.getMessage()), 439 e); 440 } 441 } 442 }