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.Collections;
041import java.util.HashSet;
042import java.util.LinkedHashMap;
043import java.util.Set;
044
045import com.unboundid.util.Debug;
046import com.unboundid.util.Mutable;
047import com.unboundid.util.NotNull;
048import com.unboundid.util.StaticUtils;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.Validator;
052import com.unboundid.util.json.JSONException;
053import com.unboundid.util.json.JSONObject;
054import com.unboundid.util.json.JSONString;
055import com.unboundid.util.json.JSONValue;
056
057import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*;
058
059
060
061/**
062 * This class provides an implementation of a JSON object filter that can
063 * negate the result of a provided filter.  If the embedded filter matches a
064 * given JSON object, then this negate filter will not match that object.  If
065 * the embedded filter does not match a JSON object, then this negate filter
066 * will match that 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 a "negate" filter are:
079 * <UL>
080 *   <LI>
081 *     {@code negateFilter} -- The JSON object filter whose match result should
082 *     be negated.
083 *   </LI>
084 * </UL>
085 * <H2>Example</H2>
086 * The following is an example of a "negate" filter that will match any JSON
087 * object that does not have a top-level field named "userType" with a value of
088 * "employee":
089 * <PRE>
090 *   { "filterType" : "negate",
091 *     "negateFilter" : {
092 *       "filterType" : "equals",
093 *       "field" : "userType",
094 *       "value" : "employee" } }
095 * </PRE>
096 * The above filter can be created with the code:
097 * <PRE>
098 *   NegateJSONObjectFilter filter = new NegateJSONObjectFilter(
099 *        new EqualsJSONObjectFilter("userType", "employee"));
100 * </PRE>
101 */
102@Mutable()
103@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
104public final class NegateJSONObjectFilter
105       extends JSONObjectFilter
106{
107  /**
108   * The value that should be used for the filterType element of the JSON object
109   * that represents a "negate" filter.
110   */
111  @NotNull public static final String FILTER_TYPE = "negate";
112
113
114
115  /**
116   * The name of the JSON field that is used to specify the filter to negate.
117   */
118  @NotNull public static final String FIELD_NEGATE_FILTER = "negateFilter";
119
120
121
122  /**
123   * The pre-allocated set of required field names.
124   */
125  @NotNull private static final Set<String> REQUIRED_FIELD_NAMES =
126       Collections.unmodifiableSet(new HashSet<>(
127            Collections.singletonList(FIELD_NEGATE_FILTER)));
128
129
130
131  /**
132   * The pre-allocated set of optional field names.
133   */
134  @NotNull private static final Set<String> OPTIONAL_FIELD_NAMES =
135       Collections.emptySet();
136
137
138
139  /**
140   * The serial version UID for this serializable class.
141   */
142  private static final long serialVersionUID = -9067967834329526711L;
143
144
145
146  // The embedded filter whose result will be negated.
147  @NotNull private volatile JSONObjectFilter negateFilter;
148
149
150
151  /**
152   * Creates an instance of this filter type that can only be used for decoding
153   * JSON objects as "negate" filters.  It cannot be used as a regular "negate"
154   * filter.
155   */
156  NegateJSONObjectFilter()
157  {
158    negateFilter = null;
159  }
160
161
162
163  /**
164   * Creates a new instance of this filter type with the provided information.
165   *
166   * @param  negateFilter  The JSON object filter whose match result should be
167   *                       negated.  It must not be {@code null}.
168   */
169  public NegateJSONObjectFilter(@NotNull final JSONObjectFilter negateFilter)
170  {
171    Validator.ensureNotNull(negateFilter);
172
173    this.negateFilter = negateFilter;
174  }
175
176
177
178  /**
179   * Retrieves the JSON object filter whose match result will be negated.
180   *
181   * @return  The JSON object filter whose match result will be negated.
182   */
183  @NotNull()
184  public JSONObjectFilter getNegateFilter()
185  {
186    return negateFilter;
187  }
188
189
190
191  /**
192   * Specifies the JSON object filter whose match result should be negated.
193   *
194   * @param  negateFilter  The JSON object filter whose match result should be
195   *                       negated.
196   */
197  public void setNegateFilter(@NotNull final JSONObjectFilter negateFilter)
198  {
199    Validator.ensureNotNull(negateFilter);
200
201    this.negateFilter = negateFilter;
202  }
203
204
205
206  /**
207   * {@inheritDoc}
208   */
209  @Override()
210  @NotNull()
211  public String getFilterType()
212  {
213    return FILTER_TYPE;
214  }
215
216
217
218  /**
219   * {@inheritDoc}
220   */
221  @Override()
222  @NotNull()
223  protected Set<String> getRequiredFieldNames()
224  {
225    return REQUIRED_FIELD_NAMES;
226  }
227
228
229
230  /**
231   * {@inheritDoc}
232   */
233  @Override()
234  @NotNull()
235  protected Set<String> getOptionalFieldNames()
236  {
237    return OPTIONAL_FIELD_NAMES;
238  }
239
240
241
242  /**
243   * {@inheritDoc}
244   */
245  @Override()
246  public boolean matchesJSONObject(@NotNull final JSONObject o)
247  {
248    return (! negateFilter.matchesJSONObject(o));
249  }
250
251
252
253  /**
254   * {@inheritDoc}
255   */
256  @Override()
257  @NotNull()
258  public JSONObject toJSONObject()
259  {
260    final LinkedHashMap<String,JSONValue> fields =
261         new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
262
263    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
264    fields.put(FIELD_NEGATE_FILTER, negateFilter.toJSONObject());
265
266    return new JSONObject(fields);
267  }
268
269
270
271  /**
272   * {@inheritDoc}
273   */
274  @Override()
275  @NotNull()
276  public JSONObject toNormalizedJSONObject()
277  {
278    final LinkedHashMap<String,JSONValue> fields =
279         new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
280
281    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
282    fields.put(FIELD_NEGATE_FILTER, negateFilter.toNormalizedJSONObject());
283
284    return new JSONObject(fields);
285  }
286
287
288
289  /**
290   * {@inheritDoc}
291   */
292  @Override()
293  @NotNull()
294  protected NegateJSONObjectFilter decodeFilter(
295                 @NotNull final JSONObject filterObject)
296            throws JSONException
297  {
298    final JSONValue v = filterObject.getField(FIELD_NEGATE_FILTER);
299    if (v == null)
300    {
301      throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
302           String.valueOf(filterObject), FILTER_TYPE, FIELD_NEGATE_FILTER));
303    }
304
305    if (! (v instanceof JSONObject))
306    {
307      throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_OBJECT.get(
308           String.valueOf(filterObject), FILTER_TYPE, FIELD_NEGATE_FILTER));
309    }
310
311    try
312    {
313      return new NegateJSONObjectFilter(
314           JSONObjectFilter.decode((JSONObject) v));
315    }
316    catch (final JSONException e)
317    {
318      Debug.debugException(e);
319      throw new JSONException(
320           ERR_OBJECT_FILTER_VALUE_NOT_FILTER.get(String.valueOf(filterObject),
321                FILTER_TYPE, FIELD_NEGATE_FILTER, e.getMessage()),
322           e);
323    }
324  }
325}