001/*
002 * Copyright 2015-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 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.Collections;
026import java.util.HashSet;
027import java.util.LinkedHashMap;
028import java.util.Set;
029
030import com.unboundid.util.Debug;
031import com.unboundid.util.Mutable;
032import com.unboundid.util.StaticUtils;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035import com.unboundid.util.Validator;
036import com.unboundid.util.json.JSONException;
037import com.unboundid.util.json.JSONObject;
038import com.unboundid.util.json.JSONString;
039import com.unboundid.util.json.JSONValue;
040
041import static com.unboundid.ldap.sdk.unboundidds.jsonfilter.JFMessages.*;
042
043
044
045/**
046 * This class provides an implementation of a JSON object filter that can
047 * negate the result of a provided filter.  If the embedded filter matches a
048 * given JSON object, then this negate filter will not match that object.  If
049 * the embedded filter does not match a JSON object, then this negate filter
050 * will match that object.
051 * <BR>
052 * <BLOCKQUOTE>
053 *   <B>NOTE:</B>  This class, and other classes within the
054 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
055 *   supported for use against Ping Identity, UnboundID, and
056 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
057 *   for proprietary functionality or for external specifications that are not
058 *   considered stable or mature enough to be guaranteed to work in an
059 *   interoperable way with other types of LDAP servers.
060 * </BLOCKQUOTE>
061 * <BR>
062 * The fields that are required to be included in a "negate" filter are:
063 * <UL>
064 *   <LI>
065 *     {@code negateFilter} -- The JSON object filter whose match result should
066 *     be negated.
067 *   </LI>
068 * </UL>
069 * <H2>Example</H2>
070 * The following is an example of a "negate" filter that will match any JSON
071 * object that does not have a top-level field named "userType" with a value of
072 * "employee":
073 * <PRE>
074 *   { "filterType" : "negate",
075 *     "negateFilter" : {
076 *       "filterType" : "equals",
077 *       "field" : "userType",
078 *       "value" : "employee" } }
079 * </PRE>
080 * The above filter can be created with the code:
081 * <PRE>
082 *   NegateJSONObjectFilter filter = new NegateJSONObjectFilter(
083 *        new EqualsJSONObjectFilter("userType", "employee"));
084 * </PRE>
085 */
086@Mutable()
087@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
088public final class NegateJSONObjectFilter
089       extends JSONObjectFilter
090{
091  /**
092   * The value that should be used for the filterType element of the JSON object
093   * that represents a "negate" filter.
094   */
095  public static final String FILTER_TYPE = "negate";
096
097
098
099  /**
100   * The name of the JSON field that is used to specify the filter to negate.
101   */
102  public static final String FIELD_NEGATE_FILTER = "negateFilter";
103
104
105
106  /**
107   * The pre-allocated set of required field names.
108   */
109  private static final Set<String> REQUIRED_FIELD_NAMES =
110       Collections.unmodifiableSet(new HashSet<>(
111            Collections.singletonList(FIELD_NEGATE_FILTER)));
112
113
114
115  /**
116   * The pre-allocated set of optional field names.
117   */
118  private static final Set<String> OPTIONAL_FIELD_NAMES =
119       Collections.emptySet();
120
121
122
123  /**
124   * The serial version UID for this serializable class.
125   */
126  private static final long serialVersionUID = -9067967834329526711L;
127
128
129
130  // The embedded filter whose result will be negated.
131  private volatile JSONObjectFilter negateFilter;
132
133
134
135  /**
136   * Creates an instance of this filter type that can only be used for decoding
137   * JSON objects as "negate" filters.  It cannot be used as a regular "negate"
138   * filter.
139   */
140  NegateJSONObjectFilter()
141  {
142    negateFilter = null;
143  }
144
145
146
147  /**
148   * Creates a new instance of this filter type with the provided information.
149   *
150   * @param  negateFilter  The JSON object filter whose match result should be
151   *                       negated.  It must not be {@code null}.
152   */
153  public NegateJSONObjectFilter(final JSONObjectFilter negateFilter)
154  {
155    Validator.ensureNotNull(negateFilter);
156
157    this.negateFilter = negateFilter;
158  }
159
160
161
162  /**
163   * Retrieves the JSON object filter whose match result will be negated.
164   *
165   * @return  The JSON object filter whose match result will be negated.
166   */
167  public JSONObjectFilter getNegateFilter()
168  {
169    return negateFilter;
170  }
171
172
173
174  /**
175   * Specifies the JSON object filter whose match result should be negated.
176   *
177   * @param  negateFilter  The JSON object filter whose match result should be
178   *                       negated.
179   */
180  public void setNegateFilter(final JSONObjectFilter negateFilter)
181  {
182    Validator.ensureNotNull(negateFilter);
183
184    this.negateFilter = negateFilter;
185  }
186
187
188
189  /**
190   * {@inheritDoc}
191   */
192  @Override()
193  public String getFilterType()
194  {
195    return FILTER_TYPE;
196  }
197
198
199
200  /**
201   * {@inheritDoc}
202   */
203  @Override()
204  protected Set<String> getRequiredFieldNames()
205  {
206    return REQUIRED_FIELD_NAMES;
207  }
208
209
210
211  /**
212   * {@inheritDoc}
213   */
214  @Override()
215  protected Set<String> getOptionalFieldNames()
216  {
217    return OPTIONAL_FIELD_NAMES;
218  }
219
220
221
222  /**
223   * {@inheritDoc}
224   */
225  @Override()
226  public boolean matchesJSONObject(final JSONObject o)
227  {
228    return (! negateFilter.matchesJSONObject(o));
229  }
230
231
232
233  /**
234   * {@inheritDoc}
235   */
236  @Override()
237  public JSONObject toJSONObject()
238  {
239    final LinkedHashMap<String,JSONValue> fields =
240         new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
241
242    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
243    fields.put(FIELD_NEGATE_FILTER, negateFilter.toJSONObject());
244
245    return new JSONObject(fields);
246  }
247
248
249
250  /**
251   * {@inheritDoc}
252   */
253  @Override()
254  protected NegateJSONObjectFilter decodeFilter(final JSONObject filterObject)
255            throws JSONException
256  {
257    final JSONValue v = filterObject.getField(FIELD_NEGATE_FILTER);
258    if (v == null)
259    {
260      throw new JSONException(ERR_OBJECT_FILTER_MISSING_REQUIRED_FIELD.get(
261           String.valueOf(filterObject), FILTER_TYPE, FIELD_NEGATE_FILTER));
262    }
263
264    if (! (v instanceof JSONObject))
265    {
266      throw new JSONException(ERR_OBJECT_FILTER_VALUE_NOT_OBJECT.get(
267           String.valueOf(filterObject), FILTER_TYPE, FIELD_NEGATE_FILTER));
268    }
269
270    try
271    {
272      return new NegateJSONObjectFilter(
273           JSONObjectFilter.decode((JSONObject) v));
274    }
275    catch (final JSONException e)
276    {
277      Debug.debugException(e);
278      throw new JSONException(
279           ERR_OBJECT_FILTER_VALUE_NOT_FILTER.get(String.valueOf(filterObject),
280                FILTER_TYPE, FIELD_NEGATE_FILTER, e.getMessage()),
281           e);
282    }
283  }
284}