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.math.BigDecimal;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.Collections;
044import java.util.HashSet;
045import java.util.LinkedHashMap;
046import java.util.List;
047import java.util.Set;
048
049import com.unboundid.util.Mutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055import com.unboundid.util.json.JSONArray;
056import com.unboundid.util.json.JSONBoolean;
057import com.unboundid.util.json.JSONException;
058import com.unboundid.util.json.JSONNumber;
059import com.unboundid.util.json.JSONObject;
060import com.unboundid.util.json.JSONString;
061import com.unboundid.util.json.JSONValue;
062
063
064
065/**
066 * This class provides an implementation of a JSON object filter that can be
067 * used to identify JSON objects that have at least one value for a specified
068 * field that is less than a given value.
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 a "less than" filter are:
081 * <UL>
082 *   <LI>
083 *     {@code field} -- A field path specifier for the JSON field for which to
084 *     make the determination.  This may be either a single string or an array
085 *     of strings as described in the "Targeting Fields in JSON Objects" section
086 *     of the class-level documentation for {@link JSONObjectFilter}.
087 *   </LI>
088 *   <LI>
089 *     {@code value} -- The value to use in the matching.  It must be either a
090 *     string (which will be compared against other strings using lexicographic
091 *     comparison) or a number.
092 *   </LI>
093 * </UL>
094 * The fields that may optionally be included in a "less than" filter are:
095 * <UL>
096 *   <LI>
097 *     {@code allowEquals} -- Indicates whether to match JSON objects that have
098 *     a value for the specified field that matches the provided value.  If
099 *     present, this field must have a Boolean value of either {@code true} (to
100 *     indicate that it should be a "less-than or equal to" filter) or
101 *     {@code false} (to indicate that it should be a strict "less-than"
102 *     filter).  If this is not specified, then the default behavior will be to
103 *     perform a strict "less-than" evaluation.
104 *   </LI>
105 *   <LI>
106 *     {@code matchAllElements} -- Indicates whether all elements of an array
107 *     must be less than (or possibly equal to) the specified value.  If
108 *     present, this field must have a Boolean value of {@code true} (to
109 *     indicate that all elements of the array must match the criteria for this
110 *     filter) or {@code false} (to indicate that at least one element of the
111 *     array must match the criteria for this filter).  If this is not
112 *     specified, then the default behavior will be to require only at least
113 *     one matching element.  This field will be ignored for JSON objects in
114 *     which the specified field has a value that is not an array.
115 *   </LI>
116 *   <LI>
117 *     {@code caseSensitive} -- Indicates whether string values should be
118 *     treated in a case-sensitive manner.  If present, this field must have a
119 *     Boolean value of either {@code true} or {@code false}.  If it is not
120 *     provided, then a default value of {@code false} will be assumed so that
121 *     strings are treated in a case-insensitive manner.
122 *   </LI>
123 * </UL>
124 * <H2>Example</H2>
125 * The following is an example of a "less than" filter that will match any
126 * JSON object with a top-level field named "loginFailureCount" with a value
127 * that is less than or equal to 3:
128 * <PRE>
129 *   { "filterType" : "lessThan",
130 *     "field" : "loginFailureCount",
131 *     "value" : 3,
132 *     "allowEquals" : true }
133 * </PRE>
134 * The above filter can be created with the code:
135 * <PRE>
136 *   LessThanJSONObjectFilter filter =
137 *        new LessThanJSONObjectFilter("loginFailureCount", 3);
138 *   filter.setAllowEquals(true);
139 * </PRE>
140 */
141@Mutable()
142@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
143public final class LessThanJSONObjectFilter
144       extends JSONObjectFilter
145{
146  /**
147   * The value that should be used for the filterType element of the JSON object
148   * that represents a "less than" filter.
149   */
150  @NotNull public static final String FILTER_TYPE = "lessThan";
151
152
153
154  /**
155   * The name of the JSON field that is used to specify the field in the target
156   * JSON object for which to make the determination.
157   */
158  @NotNull public static final String FIELD_FIELD_PATH = "field";
159
160
161
162  /**
163   * The name of the JSON field that is used to specify the value to use for
164   * the matching.
165   */
166  @NotNull public static final String FIELD_VALUE = "value";
167
168
169
170  /**
171   * The name of the JSON field that is used to indicate whether to match JSON
172   * objects with a value that is considered equal to the provided value.
173   */
174  @NotNull public static final String FIELD_ALLOW_EQUALS = "allowEquals";
175
176
177
178  /**
179   * The name of the JSON field that is used to indicate whether to match all
180   * elements of an array rather than just one or more.
181   */
182  @NotNull public static final String FIELD_MATCH_ALL_ELEMENTS =
183       "matchAllElements";
184
185
186
187  /**
188   * The name of the JSON field that is used to indicate whether string matching
189   * should be case-sensitive.
190   */
191  @NotNull public static final String FIELD_CASE_SENSITIVE = "caseSensitive";
192
193
194
195  /**
196   * The pre-allocated set of required field names.
197   */
198  @NotNull private static final Set<String> REQUIRED_FIELD_NAMES =
199       Collections.unmodifiableSet(new HashSet<>(
200            Arrays.asList(FIELD_FIELD_PATH, FIELD_VALUE)));
201
202
203
204  /**
205   * The pre-allocated set of optional field names.
206   */
207  @NotNull private static final Set<String> OPTIONAL_FIELD_NAMES =
208       Collections.unmodifiableSet(new HashSet<>(
209            Arrays.asList(FIELD_ALLOW_EQUALS, FIELD_MATCH_ALL_ELEMENTS,
210                 FIELD_CASE_SENSITIVE)));
211
212
213
214  /**
215   * The serial version UID for this serializable class.
216   */
217  private static final long serialVersionUID = -6023453566718838004L;
218
219
220
221  // Indicates whether to match equivalent values in addition to those that are
222  // strictly less than the target value.
223  private volatile boolean allowEquals;
224
225  // Indicates whether string matching should be case-sensitive.
226  private volatile boolean caseSensitive;
227
228  // Indicates whether to match all elements of an array rather than just one or
229  // more.
230  private volatile boolean matchAllElements;
231
232  // The expected value for the target field.
233  @NotNull private volatile JSONValue value;
234
235  // The field path specifier for the target field.
236  @NotNull private volatile List<String> field;
237
238
239
240  /**
241   * Creates an instance of this filter type that can only be used for decoding
242   * JSON objects as "less than" filters.  It cannot be used as a regular
243   * "less than" filter.
244   */
245  LessThanJSONObjectFilter()
246  {
247    field = null;
248    value = null;
249    allowEquals = false;
250    matchAllElements = false;
251    caseSensitive = false;
252  }
253
254
255
256  /**
257   * Creates a new instance of this filter type with the provided information.
258   *
259   * @param  field             The field path specifier for the target field.
260   * @param  value             The expected value for the target field.
261   * @param  allowEquals       Indicates whether to match values that are equal
262   *                           to the provided value in addition to those that
263   *                           are strictly less than that value.
264   * @param  matchAllElements  Indicates whether, if the value of the target
265   *                           field is an array, all elements of that array
266   *                           will be required to match the criteria of this
267   *                           filter.
268   * @param  caseSensitive     Indicates whether string matching should be
269   *                           case sensitive.
270   */
271  private LessThanJSONObjectFilter(@NotNull final List<String> field,
272                                   @NotNull final JSONValue value,
273                                   final boolean allowEquals,
274                                   final boolean matchAllElements,
275                                   final boolean caseSensitive)
276  {
277    this.field = field;
278    this.value = value;
279    this.allowEquals = allowEquals;
280    this.matchAllElements = matchAllElements;
281    this.caseSensitive = caseSensitive;
282  }
283
284
285
286  /**
287   * Creates a new instance of this filter type with the provided information.
288   *
289   * @param  field  The name of the top-level field to target with this filter.
290   *                It must not be {@code null} .  See the class-level
291   *                documentation for the {@link JSONObjectFilter} class for
292   *                information about field path specifiers.
293   * @param  value  The target value for this filter.
294   */
295  public LessThanJSONObjectFilter(@NotNull final String field, final long value)
296  {
297    this(Collections.singletonList(field), new JSONNumber(value));
298  }
299
300
301
302  /**
303   * Creates a new instance of this filter type with the provided information.
304   *
305   * @param  field  The name of the top-level field to target with this filter.
306   *                It must not be {@code null} .  See the class-level
307   *                documentation for the {@link JSONObjectFilter} class for
308   *                information about field path specifiers.
309   * @param  value  The target value for this filter.
310   */
311  public LessThanJSONObjectFilter(@NotNull final String field,
312                                  final double value)
313  {
314    this(Collections.singletonList(field), new JSONNumber(value));
315  }
316
317
318
319  /**
320   * Creates a new instance of this filter type with the provided information.
321   *
322   * @param  field  The name of the top-level field to target with this filter.
323   *                It must not be {@code null} .  See the class-level
324   *                documentation for the {@link JSONObjectFilter} class for
325   *                information about field path specifiers.
326   * @param  value  The target value for this filter.  It must not be
327   *                {@code null}.
328   */
329  public LessThanJSONObjectFilter(@NotNull final String field,
330                                  @NotNull final String value)
331  {
332    this(Collections.singletonList(field), new JSONString(value));
333  }
334
335
336
337  /**
338   * Creates a new instance of this filter type with the provided information.
339   *
340   * @param  field  The name of the top-level field to target with this filter.
341   *                It must not be {@code null} .  See the class-level
342   *                documentation for the {@link JSONObjectFilter} class for
343   *                information about field path specifiers.
344   * @param  value  The target value for this filter.  It must not be
345   *                {@code null}, and it must be either a {@link JSONNumber} or
346   *                a {@link JSONString}.
347   */
348  public LessThanJSONObjectFilter(@NotNull final String field,
349                                  @NotNull final JSONValue value)
350  {
351    this(Collections.singletonList(field), value);
352  }
353
354
355
356  /**
357   * Creates a new instance of this filter type with the provided information.
358   *
359   * @param  field  The field path specifier for this filter.  It must not be
360   *                {@code null} or empty.  See the class-level documentation
361   *                for the {@link JSONObjectFilter} class for information about
362   *                field path specifiers.
363   * @param  value  The target value for this filter.  It must not be
364   *                {@code null}, and it must be either a {@link JSONNumber} or
365   *                a {@link JSONString}.
366   */
367  public LessThanJSONObjectFilter(@NotNull final List<String> field,
368                                  @NotNull final JSONValue value)
369  {
370    Validator.ensureNotNull(field);
371    Validator.ensureFalse(field.isEmpty());
372
373    Validator.ensureNotNull(value);
374    Validator.ensureTrue((value instanceof JSONNumber) ||
375         (value instanceof JSONString));
376
377    this.field = Collections.unmodifiableList(new ArrayList<>(field));
378    this.value = value;
379
380    allowEquals = false;
381    matchAllElements = false;
382    caseSensitive = false;
383  }
384
385
386
387  /**
388   * Retrieves the field path specifier for this filter.
389   *
390   * @return  The field path specifier for this filter.
391   */
392  @NotNull()
393  public List<String> getField()
394  {
395    return field;
396  }
397
398
399
400  /**
401   * Sets the field path specifier for this filter.
402   *
403   * @param  field  The field path specifier for this filter.  It must not be
404   *                {@code null} or empty.  See the class-level documentation
405   *                for the {@link JSONObjectFilter} class for information about
406   *                field path specifiers.
407   */
408  public void setField(@NotNull final String... field)
409  {
410    setField(StaticUtils.toList(field));
411  }
412
413
414
415  /**
416   * Sets the field path specifier for this filter.
417   *
418   * @param  field  The field path specifier for this filter.  It must not be
419   *                {@code null} or empty.  See the class-level documentation
420   *                for the {@link JSONObjectFilter} class for information about
421   *                field path specifiers.
422   */
423  public void setField(@NotNull final List<String> field)
424  {
425    Validator.ensureNotNull(field);
426    Validator.ensureFalse(field.isEmpty());
427
428    this.field = Collections.unmodifiableList(new ArrayList<>(field));
429  }
430
431
432
433  /**
434   * Retrieves the target value for this filter.
435   *
436   * @return  The target value for this filter.
437   */
438  @NotNull()
439  public JSONValue getValue()
440  {
441    return value;
442  }
443
444
445
446  /**
447   * Specifies the target value for this filter.
448   *
449   * @param  value  The target value for this filter.
450   */
451  public void setValue(final long value)
452  {
453    setValue(new JSONNumber(value));
454  }
455
456
457
458  /**
459   * Specifies the target value for this filter.
460   *
461   * @param  value  The target value for this filter.
462   */
463  public void setValue(final double value)
464  {
465    setValue(new JSONNumber(value));
466  }
467
468
469
470  /**
471   * Specifies the target value for this filter.
472   *
473   * @param  value  The target value for this filter.  It must not be
474   *                {@code null}.
475   */
476  public void setValue(@NotNull final String value)
477  {
478    Validator.ensureNotNull(value);
479
480    setValue(new JSONString(value));
481  }
482
483
484
485  /**
486   * Specifies the target value for this filter.
487   *
488   * @param  value  The target value for this filter.  It must not be
489   *                {@code null}, and it must be either a {@link JSONNumber} or
490   *                a {@link JSONString}.
491   */
492  public void setValue(@NotNull final JSONValue value)
493  {
494    Validator.ensureNotNull(value);
495    Validator.ensureTrue((value instanceof JSONNumber) ||
496         (value instanceof JSONString));
497
498    this.value = value;
499  }
500
501
502
503  /**
504   * Indicates whether this filter will match values that are considered equal
505   * to the provided value in addition to those that are strictly less than
506   * that value.
507   *
508   * @return  {@code true} if this filter should behave like a "less than or
509   *          equal to" filter, or {@code false} if it should behave strictly
510   *          like a "less than" filter.
511   */
512  public boolean allowEquals()
513  {
514    return allowEquals;
515  }
516
517
518
519  /**
520   * Specifies whether this filter should match values that are considered equal
521   * to the provided value in addition to those that are strictly less than
522   * that value.
523   *
524   * @param  allowEquals  Indicates whether this filter should match values that
525   *                      are considered equal to the provided value in addition
526   *                      to those that are strictly less than this value.
527   */
528  public void setAllowEquals(final boolean allowEquals)
529  {
530    this.allowEquals = allowEquals;
531  }
532
533
534
535  /**
536   * Indicates whether, if the specified field has a value that is an array, to
537   * require all elements of that array to match the criteria for this filter
538   * rather than merely requiring at least one value to match.
539   *
540   * @return  {@code true} if the criteria contained in this filter will be
541   *          required to match all elements of an array, or {@code false} if
542   *          merely one or more values will be required to match.
543   */
544  public boolean matchAllElements()
545  {
546    return matchAllElements;
547  }
548
549
550
551  /**
552   * Specifies whether, if the value of the target field is an array, all
553   * elements of that array will be required to match the criteria of this
554   * filter.  This will be ignored if the value of the target field is not an
555   * array.
556   *
557   * @param  matchAllElements  {@code true} to indicate that all elements of an
558   *                           array will be required to match the criteria of
559   *                           this filter, or {@code false} to indicate that
560   *                           merely one or more values will be required to
561   *                           match.
562   */
563  public void setMatchAllElements(final boolean matchAllElements)
564  {
565    this.matchAllElements = matchAllElements;
566  }
567
568
569
570  /**
571   * Indicates whether string matching should be performed in a case-sensitive
572   * manner.
573   *
574   * @return  {@code true} if string matching should be case sensitive, or
575   *          {@code false} if not.
576   */
577  public boolean caseSensitive()
578  {
579    return caseSensitive;
580  }
581
582
583
584  /**
585   * Specifies whether string matching should be performed in a case-sensitive
586   * manner.
587   *
588   * @param  caseSensitive  Indicates whether string matching should be
589   *                        case sensitive.
590   */
591  public void setCaseSensitive(final boolean caseSensitive)
592  {
593    this.caseSensitive = caseSensitive;
594  }
595
596
597
598  /**
599   * {@inheritDoc}
600   */
601  @Override()
602  @NotNull()
603  public String getFilterType()
604  {
605    return FILTER_TYPE;
606  }
607
608
609
610  /**
611   * {@inheritDoc}
612   */
613  @Override()
614  @NotNull()
615  protected Set<String> getRequiredFieldNames()
616  {
617    return REQUIRED_FIELD_NAMES;
618  }
619
620
621
622  /**
623   * {@inheritDoc}
624   */
625  @Override()
626  @NotNull()
627  protected Set<String> getOptionalFieldNames()
628  {
629    return OPTIONAL_FIELD_NAMES;
630  }
631
632
633
634  /**
635   * {@inheritDoc}
636   */
637  @Override()
638  public boolean matchesJSONObject(@NotNull final JSONObject o)
639  {
640    final List<JSONValue> candidates = getValues(o, field);
641    if (candidates.isEmpty())
642    {
643      return false;
644    }
645
646    for (final JSONValue v : candidates)
647    {
648      if (v instanceof JSONArray)
649      {
650        boolean matchOne = false;
651        boolean matchAll = true;
652        for (final JSONValue arrayValue : ((JSONArray) v).getValues())
653        {
654          if (matches(arrayValue))
655          {
656            if (! matchAllElements)
657            {
658              return true;
659            }
660            matchOne = true;
661          }
662          else
663          {
664            matchAll = false;
665            if (matchAllElements)
666            {
667              break;
668            }
669          }
670        }
671
672        if (matchAllElements && matchOne && matchAll)
673        {
674          return true;
675        }
676      }
677      else if (matches(v))
678      {
679        return true;
680      }
681    }
682
683    return false;
684  }
685
686
687
688  /**
689   * Indicates whether the provided value matches the criteria of this filter.
690   *
691   * @param  v  The value for which to make the determination.
692   *
693   * @return  {@code true} if the provided value matches the criteria of this
694   *          filter, or {@code false} if not.
695   */
696  private boolean matches(@NotNull final JSONValue v)
697  {
698    if ((v instanceof JSONNumber) && (value instanceof JSONNumber))
699    {
700      final BigDecimal targetValue = ((JSONNumber) value).getValue();
701      final BigDecimal objectValue = ((JSONNumber) v).getValue();
702      if (allowEquals)
703      {
704        return (objectValue.compareTo(targetValue) <= 0);
705      }
706      else
707      {
708        return (objectValue.compareTo(targetValue) < 0);
709      }
710    }
711    else if ((v instanceof JSONString) && (value instanceof JSONString))
712    {
713      final String targetValue = ((JSONString) value).stringValue();
714      final String objectValue = ((JSONString) v).stringValue();
715      if (allowEquals)
716      {
717        if (caseSensitive)
718        {
719          return (objectValue.compareTo(targetValue) <= 0);
720        }
721        else
722        {
723          return (objectValue.compareToIgnoreCase(targetValue) <= 0);
724        }
725      }
726      else
727      {
728        if (caseSensitive)
729        {
730          return (objectValue.compareTo(targetValue) < 0);
731        }
732        else
733        {
734          return (objectValue.compareToIgnoreCase(targetValue) < 0);
735        }
736      }
737    }
738    else
739    {
740      return false;
741    }
742  }
743
744
745
746  /**
747   * {@inheritDoc}
748   */
749  @Override()
750  @NotNull()
751  public JSONObject toJSONObject()
752  {
753    final LinkedHashMap<String,JSONValue> fields =
754         new LinkedHashMap<>(StaticUtils.computeMapCapacity(6));
755
756    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
757
758    if (field.size() == 1)
759    {
760      fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0)));
761    }
762    else
763    {
764      final ArrayList<JSONValue> fieldNameValues =
765           new ArrayList<>(field.size());
766      for (final String s : field)
767      {
768        fieldNameValues.add(new JSONString(s));
769      }
770      fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues));
771    }
772
773    fields.put(FIELD_VALUE, value);
774
775    if (allowEquals)
776    {
777      fields.put(FIELD_ALLOW_EQUALS, JSONBoolean.TRUE);
778    }
779
780    if (matchAllElements)
781    {
782      fields.put(FIELD_MATCH_ALL_ELEMENTS, JSONBoolean.TRUE);
783    }
784
785    if (caseSensitive)
786    {
787      fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE);
788    }
789
790    return new JSONObject(fields);
791  }
792
793
794
795  /**
796   * {@inheritDoc}
797   */
798  @Override()
799  @NotNull()
800  public JSONObject toNormalizedJSONObject()
801  {
802    final LinkedHashMap<String,JSONValue> fields =
803         new LinkedHashMap<>(StaticUtils.computeMapCapacity(6));
804
805    fields.put(FIELD_FILTER_TYPE, new JSONString(FILTER_TYPE));
806
807    if (field.size() == 1)
808    {
809      fields.put(FIELD_FIELD_PATH, new JSONString(field.get(0)));
810    }
811    else
812    {
813      final ArrayList<JSONValue> fieldNameValues =
814           new ArrayList<>(field.size());
815      for (final String s : field)
816      {
817        fieldNameValues.add(new JSONString(s));
818      }
819      fields.put(FIELD_FIELD_PATH, new JSONArray(fieldNameValues));
820    }
821
822    fields.put(FIELD_VALUE,
823         value.toNormalizedValue(false, (! caseSensitive), false));
824
825    if (allowEquals)
826    {
827      fields.put(FIELD_ALLOW_EQUALS, JSONBoolean.TRUE);
828    }
829
830    if (matchAllElements)
831    {
832      fields.put(FIELD_MATCH_ALL_ELEMENTS, JSONBoolean.TRUE);
833    }
834
835    if (caseSensitive)
836    {
837      fields.put(FIELD_CASE_SENSITIVE, JSONBoolean.TRUE);
838    }
839
840    return new JSONObject(fields);
841  }
842
843
844
845  /**
846   * {@inheritDoc}
847   */
848  @Override()
849  @NotNull()
850  protected LessThanJSONObjectFilter decodeFilter(
851                 @NotNull final JSONObject filterObject)
852            throws JSONException
853  {
854    final List<String> fieldPath =
855         getStrings(filterObject, FIELD_FIELD_PATH, false, null);
856
857    final boolean isAllowEquals = getBoolean(filterObject,
858         FIELD_ALLOW_EQUALS, false);
859
860    final boolean isMatchAllElements = getBoolean(filterObject,
861         FIELD_MATCH_ALL_ELEMENTS, false);
862
863    final boolean isCaseSensitive = getBoolean(filterObject,
864         FIELD_CASE_SENSITIVE, false);
865
866    return new LessThanJSONObjectFilter(fieldPath,
867         filterObject.getField(FIELD_VALUE), isAllowEquals, isMatchAllElements,
868         isCaseSensitive);
869  }
870}