001/*
002 * Copyright 2011-2016 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018package com.unboundid.scim.sdk;
019
020import org.json.JSONException;
021import org.json.JSONObject;
022
023import java.util.ArrayList;
024import java.util.List;
025
026
027
028/**
029 * This class represents a SCIM query filter.
030 */
031public class SCIMFilter
032{
033  /**
034   * The filter type.
035   */
036  private final SCIMFilterType filterType;
037
038  /**
039   * The attribute or sub-attribute to filter by, or {@code null} if not
040   * applicable.
041   */
042  private final AttributePath filterAttribute;
043
044  /**
045   * The filter attribute value, or {@code null} if not applicable.
046   */
047  private final String filterValue;
048
049  /**
050   * Specifies whether the filter value is quoted in the string representation
051   * of the filter. String and DateTime values are quoted. Integer and Boolean
052   * values are not quoted.
053   */
054  private final boolean quoteFilterValue;
055
056  /**
057   * The filter components for 'or' and 'and' filter types, or {@code null}
058   * if not applicable.
059   */
060  private final List<SCIMFilter> filterComponents;
061
062
063
064  /**
065   * Create a new SCIM filter from the provided information.
066   *
067   * @param filterType        The filter type.
068   * @param filterAttribute   The attribute or sub-attribute to filter by, or
069   *                          {@code null} if not applicable.
070   * @param filterValue       The filter attribute value, or {@code null} if not
071   *                          applicable.
072   * @param quoteFilterValue  Specifies whether the filter value is quoted in
073   *                          the string representation of the filter.
074   * @param filterComponents  The filter components for 'or' and 'and' filter
075   *                          types, or {@code null} if not applicable.
076   */
077  public SCIMFilter(final SCIMFilterType filterType,
078                    final AttributePath filterAttribute,
079                    final String filterValue,
080                    final boolean quoteFilterValue,
081                    final List<SCIMFilter> filterComponents)
082  {
083    this.filterType        = filterType;
084    this.filterAttribute   = filterAttribute;
085    this.filterValue       = filterValue;
086    this.quoteFilterValue  = quoteFilterValue;
087    this.filterComponents  = filterComponents;
088  }
089
090
091
092  /**
093   * Parse a filter from its string representation.
094   *
095   * @param filterString  The string representation of the filter expression.
096   *
097   * @return  The parsed filter.
098   *
099   * @throws  SCIMException  If the filter string could not be parsed.
100   */
101  public static SCIMFilter parse(final String filterString)
102      throws SCIMException
103  {
104    final FilterParser parser = new FilterParser(filterString,
105                                                 SCIMConstants.SCHEMA_URI_CORE);
106    return parser.parse();
107  }
108
109
110
111  /**
112   * Parse a filter from its string representation.
113   *
114   * @param filterString  The string representation of the filter expression.
115   * @param defaultSchema The default schema that should be assumed when parsing
116   *                      attributes without the schema explicitly defined in
117   *                      the URN.
118   *
119   * @return  The parsed filter.
120   *
121   * @throws  SCIMException  If the filter string could not be parsed.
122   */
123  public static SCIMFilter parse(final String filterString,
124                                 final String defaultSchema)
125          throws SCIMException
126  {
127    final FilterParser parser = new FilterParser(filterString, defaultSchema);
128    return parser.parse();
129  }
130
131
132
133  /**
134   * Create a new and filter.
135   *
136   * @param filterComponents  The filter components.
137   *
138   * @return  A new and filter.
139   */
140  public static SCIMFilter createAndFilter(
141      final List<SCIMFilter> filterComponents)
142  {
143    return new SCIMFilter(SCIMFilterType.AND, null, null, false,
144                          new ArrayList<SCIMFilter>(filterComponents));
145  }
146
147
148
149  /**
150   * Create a new or filter.
151   *
152   * @param filterComponents  The filter components.
153   *
154   * @return  A new or filter.
155   */
156  public static SCIMFilter createOrFilter(
157      final List<SCIMFilter> filterComponents)
158  {
159    return new SCIMFilter(SCIMFilterType.OR, null, null, false,
160                          new ArrayList<SCIMFilter>(filterComponents));
161  }
162
163
164
165  /**
166   * Create a new equality filter.
167   *
168   * @param filterAttribute  The attribute or sub-attribute to filter by.
169   * @param filterValue      The filter attribute value.
170   *
171   * @return  A new equality filter.
172   */
173  public static SCIMFilter createEqualityFilter(
174      final AttributePath filterAttribute, final String filterValue)
175  {
176    return new SCIMFilter(SCIMFilterType.EQUALITY, filterAttribute,
177                          filterValue, true, null);
178  }
179
180
181
182  /**
183   * Create a new contains filter.
184   *
185   * @param filterAttribute  The attribute or sub-attribute to filter by.
186   * @param filterValue      The filter attribute value.
187   *
188   * @return  A new contains filter.
189   */
190  public static SCIMFilter createContainsFilter(
191      final AttributePath filterAttribute, final String filterValue)
192  {
193    return new SCIMFilter(SCIMFilterType.CONTAINS, filterAttribute,
194                          filterValue, true, null);
195  }
196
197
198
199  /**
200   * Create a new starts with filter.
201   *
202   * @param filterAttribute  The attribute or sub-attribute to filter by.
203   * @param filterValue      The filter attribute value.
204   *
205   * @return  A new starts with filter.
206   */
207  public static SCIMFilter createStartsWithFilter(
208      final AttributePath filterAttribute, final String filterValue)
209  {
210    return new SCIMFilter(SCIMFilterType.STARTS_WITH, filterAttribute,
211                          filterValue, true, null);
212  }
213
214
215
216  /**
217   * Create a new presence filter.
218   *
219   * @param filterAttribute  The attribute or sub-attribute to filter by.
220   *
221   * @return  A new presence filter.
222   */
223  public static SCIMFilter createPresenceFilter(
224      final AttributePath filterAttribute)
225  {
226    return new SCIMFilter(SCIMFilterType.PRESENCE, filterAttribute,
227                          null, true, null);
228  }
229
230
231
232  /**
233   * Create a new greater than filter.
234   *
235   * @param filterAttribute  The attribute or sub-attribute to filter by.
236   * @param filterValue      The filter attribute value.
237   *
238   * @return  A new greater than filter.
239   */
240  public static SCIMFilter createGreaterThanFilter(
241      final AttributePath filterAttribute, final String filterValue)
242  {
243    return new SCIMFilter(SCIMFilterType.GREATER_THAN, filterAttribute,
244                          filterValue, true, null);
245  }
246
247
248
249  /**
250   * Create a new greater or equal filter.
251   *
252   * @param filterAttribute  The attribute or sub-attribute to filter by.
253   * @param filterValue      The filter attribute value.
254   *
255   * @return  A new greater or equal filter.
256   */
257  public static SCIMFilter createGreaterOrEqualFilter(
258      final AttributePath filterAttribute, final String filterValue)
259  {
260    return new SCIMFilter(SCIMFilterType.GREATER_OR_EQUAL, filterAttribute,
261                          filterValue, true, null);
262  }
263
264
265
266  /**
267   * Create a new less than filter.
268   *
269   * @param filterAttribute  The attribute or sub-attribute to filter by.
270   * @param filterValue      The filter attribute value.
271   *
272   * @return  A new less than filter.
273   */
274  public static SCIMFilter createLessThanFilter(
275      final AttributePath filterAttribute, final String filterValue)
276  {
277    return new SCIMFilter(SCIMFilterType.LESS_THAN, filterAttribute,
278                          filterValue, true, null);
279  }
280
281
282
283  /**
284   * Create a new less or equal filter.
285   *
286   * @param filterAttribute  The attribute or sub-attribute to filter by.
287   * @param filterValue      The filter attribute value.
288   *
289   * @return  A new less or equal filter.
290   */
291  public static SCIMFilter createLessOrEqualFilter(
292      final AttributePath filterAttribute, final String filterValue)
293  {
294    return new SCIMFilter(SCIMFilterType.LESS_OR_EQUAL, filterAttribute,
295                          filterValue, true, null);
296  }
297
298
299
300  /**
301   * Retrieve the filter type.
302   *
303   * @return  The filter type.
304   */
305  public SCIMFilterType getFilterType()
306  {
307    return filterType;
308  }
309
310
311
312  /**
313   * Retrieve the attribute or sub-attribute to filter by, or {@code null} if
314   * not applicable for this filter type.
315   *
316   * @return  The attribute or sub-attribute to filter by
317   */
318  public AttributePath getFilterAttribute()
319  {
320    return filterAttribute;
321  }
322
323
324
325  /**
326   * Retrieve the filter attribute value.
327   *
328   * @return  The filter attribute value, or {@code null} if not applicable
329   *          for this filter type.
330   */
331  public String getFilterValue()
332  {
333    return filterValue;
334  }
335
336
337
338  /**
339   * Determine whether the filter attribute value is quoted in the string
340   * representation of the filter.
341   *
342   * @return {@code true} if the filter attribute value is quoted in the string
343   *         representation of the filter.
344   */
345  public boolean isQuoteFilterValue()
346  {
347    return quoteFilterValue;
348  }
349
350
351
352  /**
353   * Retrieve the filter components for an 'and' or 'or' filter.
354   *
355   * @return  The filter components for an 'and' or 'or' filter.
356   */
357  public List<SCIMFilter> getFilterComponents()
358  {
359    return filterComponents;
360  }
361
362
363
364  /**
365   * {@inheritDoc}
366   */
367  @Override
368  public String toString()
369  {
370    final StringBuilder builder = new StringBuilder();
371    toString(builder);
372    return builder.toString();
373  }
374
375
376
377  /**
378   * Append the string representation of the filter to the provided buffer.
379   *
380   * @param builder  The buffer to which the string representation of the
381   *                 filter is to be appended.
382   */
383  public void toString(final StringBuilder builder)
384  {
385    switch (filterType)
386    {
387      case AND:
388      case OR:
389        builder.append('(');
390
391        for (int i = 0; i < filterComponents.size(); i++)
392        {
393          if (i != 0)
394          {
395            builder.append(' ');
396            builder.append(filterType);
397            builder.append(' ');
398          }
399
400          builder.append(filterComponents.get(i));
401        }
402
403        builder.append(')');
404        break;
405
406      case EQUALITY:
407      case CONTAINS:
408      case STARTS_WITH:
409      case GREATER_THAN:
410      case GREATER_OR_EQUAL:
411      case LESS_THAN:
412      case LESS_OR_EQUAL:
413        builder.append(filterAttribute);
414        builder.append(' ');
415        builder.append(filterType);
416        builder.append(' ');
417
418        if (quoteFilterValue)
419        {
420          try
421          {
422            builder.append(JSONObject.valueToString(filterValue));
423          }
424          catch (JSONException e)
425          {
426            Debug.debugException(e);
427          }
428        }
429        else
430        {
431          builder.append(filterValue);
432        }
433        break;
434
435      case PRESENCE:
436        builder.append(filterAttribute);
437        builder.append(' ');
438        builder.append(filterType);
439        break;
440    }
441  }
442}