001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-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) 2007-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.controls;
037
038
039
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Map;
043
044import com.unboundid.asn1.ASN1Element;
045import com.unboundid.asn1.ASN1Integer;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.asn1.ASN1Sequence;
048import com.unboundid.ldap.sdk.Control;
049import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.ResultCode;
052import com.unboundid.util.Base64;
053import com.unboundid.util.Debug;
054import com.unboundid.util.NotMutable;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.Validator;
061import com.unboundid.util.json.JSONField;
062import com.unboundid.util.json.JSONNumber;
063import com.unboundid.util.json.JSONObject;
064import com.unboundid.util.json.JSONString;
065import com.unboundid.util.json.JSONValue;
066
067import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
068
069
070
071/**
072 * This class provides an implementation of the LDAP virtual list view (VLV)
073 * request control as defined in draft-ietf-ldapext-ldapv3-vlv.  This control
074 * may be used to retrieve arbitrary "pages" of entries from the complete set of
075 * search results.  It is similar to the {@link SimplePagedResultsControl}, with
076 * the exception that the simple paged results control requires scrolling
077 * through the results in sequential order, while the VLV control allows
078 * starting and resuming at any arbitrary point in the result set.  The starting
079 * point may be specified using either a positional offset, or based on the
080 * first entry with a value that is greater than or equal to a specified value.
081 * <BR><BR>
082 * When the start of the result set is to be specified using an offset, then the
083 * virtual list view request control should include the following elements:
084 * <UL>
085 *   <LI>{@code targetOffset} -- The position in the result set of the entry to
086 *       target for the next page of results to return.  Note that the offset is
087 *       one-based (so the first entry has offset 1, the second entry has offset
088 *       2, etc.).</LI>
089 *   <LI>{@code beforeCount} -- The number of entries before the entry specified
090 *       as the target offset that should be retrieved.</LI>
091 *   <LI>{@code afterCount} -- The number of entries after the entry specified
092 *       as the target offset that should be retrieved.</LI>
093 *   <LI>{@code contentCount} -- The estimated total number of entries that
094 *       are in the total result set.  This should be zero for the first request
095 *       in a VLV search sequence, but should be the value returned by the
096 *       server in the corresponding response control for subsequent searches as
097 *       part of the VLV sequence.</LI>
098 *   <LI>{@code contextID} -- This is an optional cookie that may be used to
099 *       help the server resume processing on a VLV search.  It should be absent
100 *       from the initial request, but for subsequent requests should be the
101 *       value returned in the previous VLV response control.</LI>
102 * </UL>
103 * When the start of the result set is to be specified using a search string,
104 * then the virtual list view request control should include the following
105 * elements:
106 * <UL>
107 *   <LI>{@code assertionValue} -- The value that specifies the start of the
108 *       page of results to retrieve.  The target entry will be the first entry
109 *       in which the value for the primary sort attribute is greater than or
110 *       equal to this assertion value.</LI>
111 *   <LI>{@code beforeCount} -- The number of entries before the entry specified
112 *        by the assertion value that should be retrieved.</LI>
113 *   <LI>{@code afterCount} -- The number of entries after the entry specified
114 *       by the assertion value that should be retrieved.</LI>
115 *   <LI>{@code contextID} -- This is an optional cookie that may be used to
116 *       help the server resume processing on a VLV search.  It should be absent
117 *       from the initial request, but for subsequent requests should be the
118 *       value returned in the previous VLV response control.</LI>
119 * </UL>
120 * Note that the virtual list view request control may only be included in a
121 * search request if that search request also includes the
122 * {@link ServerSideSortRequestControl}.  This is necessary to ensure that a
123 * consistent order is used for the resulting entries.
124 * <BR><BR>
125 * If the search is successful, then the search result done response may include
126 * a {@link VirtualListViewResponseControl} to provide information about the
127 * state of the virtual list view processing.
128 * <BR><BR>
129 * <H2>Example</H2>
130 * The following example demonstrates the use of the virtual list view request
131 * control to iterate through all users, retrieving up to 10 entries at a time:
132 * <PRE>
133 * // Perform a search to retrieve all users in the server, but only retrieving
134 * // ten at a time.  Ensure that the users are sorted in ascending order by
135 * // last name, then first name.
136 * int numSearches = 0;
137 * int totalEntriesReturned = 0;
138 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
139 *      SearchScope.SUB, Filter.createEqualityFilter("objectClass", "person"));
140 * int vlvOffset = 1;
141 * int vlvContentCount = 0;
142 * ASN1OctetString vlvContextID = null;
143 * while (true)
144 * {
145 *   // Note that the VLV control always requires the server-side sort
146 *   // control.
147 *   searchRequest.setControls(
148 *        new ServerSideSortRequestControl(new SortKey("sn"),
149 *             new SortKey("givenName")),
150 *        new VirtualListViewRequestControl(vlvOffset, 0, 9, vlvContentCount,
151 *             vlvContextID));
152 *   SearchResult searchResult = connection.search(searchRequest);
153 *   numSearches++;
154 *   totalEntriesReturned += searchResult.getEntryCount();
155 *   for (SearchResultEntry e : searchResult.getSearchEntries())
156 *   {
157 *     // Do something with each entry...
158 *   }
159 *
160 *   LDAPTestUtils.assertHasControl(searchResult,
161 *        VirtualListViewResponseControl.VIRTUAL_LIST_VIEW_RESPONSE_OID);
162 *   VirtualListViewResponseControl vlvResponseControl =
163 *        VirtualListViewResponseControl.get(searchResult);
164 *   vlvContentCount = vlvResponseControl.getContentCount();
165 *   vlvOffset += 10;
166 *   vlvContextID = vlvResponseControl.getContextID();
167 *   if (vlvOffset &gt; vlvContentCount)
168 *   {
169 *     break;
170 *   }
171 * }
172 * </PRE>
173 */
174@NotMutable()
175@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
176public final class VirtualListViewRequestControl
177       extends Control
178{
179  /**
180   * The OID (2.16.840.1.113730.3.4.9) for the virtual list view request
181   * control.
182   */
183  @NotNull public static final String VIRTUAL_LIST_VIEW_REQUEST_OID =
184       "2.16.840.1.113730.3.4.9";
185
186
187
188  /**
189   * The BER type that will be used for the target element when the target is
190   * specified by offset.
191   */
192  private static final byte TARGET_TYPE_OFFSET = (byte) 0xA0;
193
194
195
196  /**
197   * The BER type that will be used for the target element when the target is
198   * specified by an assertion value.
199   */
200  private static final byte TARGET_TYPE_GREATER_OR_EQUAL = (byte) 0x81;
201
202
203
204  /**
205   * The name of the field used to hold the after count in the JSON
206   * representation of this control.
207   */
208  @NotNull private static final String JSON_FIELD_AFTER_COUNT = "after-count";
209
210
211
212  /**
213   * The name of the field used to hold the assertion value in the JSON
214   * representation of this control.
215   */
216  @NotNull private static final String JSON_FIELD_ASSERTION_VALUE =
217       "assertion-value";
218
219
220
221  /**
222   * The name of the field used to hold the before count in the JSON
223   * representation of this control.
224   */
225  @NotNull private static final String JSON_FIELD_BEFORE_COUNT = "before-count";
226
227
228
229  /**
230   * The name of the field used to hold the content count in the JSON
231   * representation of this control.
232   */
233  @NotNull private static final String JSON_FIELD_CONTENT_COUNT =
234       "content-count";
235
236
237
238  /**
239   * The name of the field used to hold the context ID in the JSON
240   * representation of this control.
241   */
242  @NotNull private static final String JSON_FIELD_CONTEXT_ID = "context-id";
243
244
245
246  /**
247   * The name of the field used to hold the target offset in the JSON
248   * representation of this control.
249   */
250  @NotNull private static final String JSON_FIELD_TARGET_OFFSET =
251       "target-offset";
252
253
254
255  /**
256   * The serial version UID for this serializable class.
257   */
258  private static final long serialVersionUID = 4348423177859960815L;
259
260
261
262  // The assertion value that will be used to identify the start of the
263  // requested page of results for a greater-or-equal target type.
264  @Nullable private final ASN1OctetString assertionValue;
265
266  // The context ID that may be used to help the server continue in the same
267  // result set for subsequent searches.
268  @Nullable private final ASN1OctetString contextID;
269
270  // The maximum number of entries to return after the target entry.
271  private final int afterCount;
272
273  // The maximum number of entries to return before the target entry.
274  private final int beforeCount;
275
276  // The estimated number of entries in the complete result set.
277  private final int contentCount;
278
279  // The position of the entry at the start of the requested page of results for
280  // an offset-based target type.
281  private final int targetOffset;
282
283
284
285  /**
286   * Creates a new virtual list view request control that will identify the
287   * beginning of the result set by a target offset.  It will be marked
288   * critical.
289   *
290   * @param  targetOffset  The position of the entry that should be used as the
291   *                       start of the result set.
292   * @param  beforeCount   The maximum number of entries that should be returned
293   *                       before the entry with the specified target offset.
294   * @param  afterCount    The maximum number of entries that should be returned
295   *                       after the entry with the specified target offset.
296   * @param  contentCount  The estimated number of entries in the result set.
297   *                       For the first request in a series of searches with
298   *                       the VLV control, it should be zero.  For subsequent
299   *                       searches in the VLV sequence, it should be the
300   *                       content count included in the response control from
301   *                       the previous search.
302   * @param  contextID     The context ID that may be used to help the server
303   *                       continue in the same result set for subsequent
304   *                       searches.  For the first request in a series of
305   *                       searches with the VLV control, it should be
306   *                       {@code null}.  For subsequent searches in the VLV
307   *                       sequence, it should be the (possibly {@code null})
308   *                       context ID included in the response control from the
309   *                       previous search.
310   */
311  public VirtualListViewRequestControl(final int targetOffset,
312              final int beforeCount, final int afterCount,
313              final int contentCount,
314              @Nullable final ASN1OctetString contextID)
315  {
316    this(targetOffset, beforeCount, afterCount, contentCount, contextID, true);
317  }
318
319
320
321  /**
322   * Creates a new virtual list view request control that will identify the
323   * beginning of the result set by an assertion value.  It will be marked
324   * critical.
325   *
326   * @param  assertionValue  The assertion value that will be used to identify
327   *                         the start of the result set.  The target entry will
328   *                         be the first entry with a value for the primary
329   *                         sort attribute that is greater than or equal to
330   *                         this assertion value.  It must not be {@code null}.
331   * @param  beforeCount     The maximum number of entries that should be
332   *                         returned before the first entry with a value
333   *                         greater than or equal to the provided assertion
334   *                         value.
335   * @param  afterCount      The maximum number of entries that should be
336   *                         returned after the first entry with a value
337   *                         greater than or equal to the provided assertion
338   *                         value.
339   * @param  contextID       The context ID that may be used to help the server
340   *                         continue in the same result set for subsequent
341   *                         searches.  For the first request in a series of
342   *                         searches with the VLV control, it should be
343   *                         {@code null}.  For subsequent searches in the VLV
344   *                         sequence, it should be the (possibly {@code null})
345   *                         context ID included in the response control from
346   *                         the previous search.
347   */
348  public VirtualListViewRequestControl(@NotNull final String assertionValue,
349              final int beforeCount, final int afterCount,
350              @Nullable final ASN1OctetString contextID)
351  {
352    this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
353         contextID, true);
354  }
355
356
357
358  /**
359   * Creates a new virtual list view request control that will identify the
360   * beginning of the result set by an assertion value.  It will be marked
361   * critical.
362   *
363   * @param  assertionValue  The assertion value that will be used to identify
364   *                         the start of the result set.  The target entry will
365   *                         be the first entry with a value for the primary
366   *                         sort attribute that is greater than or equal to
367   *                         this assertion value.  It must not be {@code null}.
368   * @param  beforeCount     The maximum number of entries that should be
369   *                         returned before the first entry with a value
370   *                         greater than or equal to the provided assertion
371   *                         value.
372   * @param  afterCount      The maximum number of entries that should be
373   *                         returned after the first entry with a value
374   *                         greater than or equal to the provided assertion
375   *                         value.
376   * @param  contextID       The context ID that may be used to help the server
377   *                         continue in the same result set for subsequent
378   *                         searches.  For the first request in a series of
379   *                         searches with the VLV control, it should be
380   *                         {@code null}.  For subsequent searches in the VLV
381   *                         sequence, it should be the (possibly {@code null})
382   *                         context ID included in the response control from
383   *                         the previous search.
384   */
385  public VirtualListViewRequestControl(@NotNull final byte[] assertionValue,
386              final int beforeCount, final int afterCount,
387              @Nullable final ASN1OctetString contextID)
388  {
389    this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
390         contextID, true);
391  }
392
393
394
395  /**
396   * Creates a new virtual list view request control that will identify the
397   * beginning of the result set by an assertion value.  It will be marked
398   * critical.
399   *
400   * @param  assertionValue  The assertion value that will be used to identify
401   *                         the start of the result set.  The target entry will
402   *                         be the first entry with a value for the primary
403   *                         sort attribute that is greater than or equal to
404   *                         this assertion value.  It must not be {@code null}.
405   * @param  beforeCount     The maximum number of entries that should be
406   *                         returned before the first entry with a value
407   *                         greater than or equal to the provided assertion
408   *                         value.
409   * @param  afterCount      The maximum number of entries that should be
410   *                         returned after the first entry with a value
411   *                         greater than or equal to the provided assertion
412   *                         value.
413   * @param  contextID       The context ID that may be used to help the server
414   *                         continue in the same result set for subsequent
415   *                         searches.  For the first request in a series of
416   *                         searches with the VLV control, it should be
417   *                         {@code null}.  For subsequent searches in the VLV
418   *                         sequence, it should be the (possibly {@code null})
419   *                         context ID included in the response control from
420   *                         the previous search.
421   */
422  public VirtualListViewRequestControl(
423              @NotNull final ASN1OctetString assertionValue,
424              final int beforeCount, final int afterCount,
425              @Nullable final ASN1OctetString contextID)
426  {
427    this(assertionValue, beforeCount, afterCount, contextID, true);
428  }
429
430
431
432  /**
433   * Creates a new virtual list view request control that will identify the
434   * beginning of the result set by a target offset.
435   *
436   * @param  targetOffset  The position of the entry that should be used as the
437   *                       start of the result set.
438   * @param  beforeCount   The maximum number of entries that should be returned
439   *                       before the entry with the specified target offset.
440   * @param  afterCount    The maximum number of entries that should be returned
441   *                       after the entry with the specified target offset.
442   * @param  contentCount  The estimated number of entries in the result set.
443   *                       For the first request in a series of searches with
444   *                       the VLV control, it should be zero.  For subsequent
445   *                       searches in the VLV sequence, it should be the
446   *                       content count included in the response control from
447   *                       the previous search.
448   * @param  contextID     The context ID that may be used to help the server
449   *                       continue in the same result set for subsequent
450   *                       searches.  For the first request in a series of
451   *                       searches with the VLV control, it should be
452   *                       {@code null}.  For subsequent searches in the VLV
453   *                       sequence, it should be the (possibly {@code null})
454   *                       context ID included in the response control from the
455   *                       previous search.
456   * @param  isCritical    Indicates whether this control should be marked
457   *                       critical.
458   */
459  public VirtualListViewRequestControl(final int targetOffset,
460              final int beforeCount, final int afterCount,
461              final int contentCount,
462              @Nullable final ASN1OctetString contextID,
463              final boolean isCritical)
464  {
465    super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical,
466          encodeValue(targetOffset, beforeCount, afterCount, contentCount,
467                      contextID));
468
469    this.targetOffset = targetOffset;
470    this.beforeCount  = beforeCount;
471    this.afterCount   = afterCount;
472    this.contentCount = contentCount;
473    this.contextID    = contextID;
474
475    assertionValue = null;
476  }
477
478
479
480  /**
481   * Creates a new virtual list view request control that will identify the
482   * beginning of the result set by an assertion value.  It will be marked
483   * critical.
484   *
485   * @param  assertionValue  The assertion value that will be used to identify
486   *                         the start of the result set.  The target entry will
487   *                         be the first entry with a value for the primary
488   *                         sort attribute that is greater than or equal to
489   *                         this assertion value.  It must not be {@code null}.
490   * @param  beforeCount     The maximum number of entries that should be
491   *                         returned before the first entry with a value
492   *                         greater than or equal to the provided assertion
493   *                         value.
494   * @param  afterCount      The maximum number of entries that should be
495   *                         returned after the first entry with a value
496   *                         greater than or equal to the provided assertion
497   *                         value.
498   * @param  contextID       The context ID that may be used to help the server
499   *                         continue in the same result set for subsequent
500   *                         searches.  For the first request in a series of
501   *                         searches with the VLV control, it should be
502   *                         {@code null}.  For subsequent searches in the VLV
503   *                         sequence, it should be the (possibly {@code null})
504   *                         context ID included in the response control from
505   *                         the previous search.
506   * @param  isCritical    Indicates whether this control should be marked
507   *                       critical.
508   */
509  public VirtualListViewRequestControl(@NotNull final String assertionValue,
510              final int beforeCount, final int afterCount,
511              @Nullable final ASN1OctetString contextID,
512              final boolean isCritical)
513  {
514    this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
515                             contextID, isCritical);
516  }
517
518
519
520  /**
521   * Creates a new virtual list view request control that will identify the
522   * beginning of the result set by an assertion value.  It will be marked
523   * critical.
524   *
525   * @param  assertionValue  The assertion value that will be used to identify
526   *                         the start of the result set.  The target entry will
527   *                         be the first entry with a value for the primary
528   *                         sort attribute that is greater than or equal to
529   *                         this assertion value.  It must not be {@code null}.
530   * @param  beforeCount     The maximum number of entries that should be
531   *                         returned before the first entry with a value
532   *                         greater than or equal to the provided assertion
533   *                         value.
534   * @param  afterCount      The maximum number of entries that should be
535   *                         returned after the first entry with a value
536   *                         greater than or equal to the provided assertion
537   *                         value.
538   * @param  contextID       The context ID that may be used to help the server
539   *                         continue in the same result set for subsequent
540   *                         searches.  For the first request in a series of
541   *                         searches with the VLV control, it should be
542   *                         {@code null}.  For subsequent searches in the VLV
543   *                         sequence, it should be the (possibly {@code null})
544   *                         context ID included in the response control from
545   *                         the previous search.
546   * @param  isCritical    Indicates whether this control should be marked
547   *                       critical.
548   */
549  public VirtualListViewRequestControl(@NotNull final byte[] assertionValue,
550              final int beforeCount, final int afterCount,
551              @Nullable final ASN1OctetString contextID,
552              final boolean isCritical)
553  {
554    this(new ASN1OctetString(assertionValue), beforeCount, afterCount,
555                             contextID, isCritical);
556  }
557
558
559
560  /**
561   * Creates a new virtual list view request control that will identify the
562   * beginning of the result set by an assertion value.  It will be marked
563   * critical.
564   *
565   * @param  assertionValue  The assertion value that will be used to identify
566   *                         the start of the result set.  The target entry will
567   *                         be the first entry with a value for the primary
568   *                         sort attribute that is greater than or equal to
569   *                         this assertion value.  It must not be {@code null}.
570   * @param  beforeCount     The maximum number of entries that should be
571   *                         returned before the first entry with a value
572   *                         greater than or equal to the provided assertion
573   *                         value.
574   * @param  afterCount      The maximum number of entries that should be
575   *                         returned after the first entry with a value
576   *                         greater than or equal to the provided assertion
577   *                         value.
578   * @param  contextID       The context ID that may be used to help the server
579   *                         continue in the same result set for subsequent
580   *                         searches.  For the first request in a series of
581   *                         searches with the VLV control, it should be
582   *                         {@code null}.  For subsequent searches in the VLV
583   *                         sequence, it should be the (possibly {@code null})
584   *                         context ID included in the response control from
585   *                         the previous search.
586   * @param  isCritical    Indicates whether this control should be marked
587   *                       critical.
588   */
589  public VirtualListViewRequestControl(
590              @NotNull final ASN1OctetString assertionValue,
591              final int beforeCount, final int afterCount,
592              @Nullable final ASN1OctetString contextID,
593              final boolean isCritical)
594  {
595    super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical,
596          encodeValue(assertionValue, beforeCount, afterCount, contextID));
597
598    this.assertionValue = assertionValue;
599    this.beforeCount    = beforeCount;
600    this.afterCount     = afterCount;
601    this.contextID      = contextID;
602
603    targetOffset = -1;
604    contentCount = -1;
605  }
606
607
608
609  /**
610   * Creates a new virtual list view request control which is decoded from the
611   * provided generic control.
612   *
613   * @param  control  The generic control to be decoded as a virtual list view
614   *                  request control.
615   *
616   * @throws  LDAPException  If the provided control cannot be decoded as a
617   *                         virtual list view request control.
618   */
619  public VirtualListViewRequestControl(@NotNull final Control control)
620         throws LDAPException
621  {
622    super(control);
623
624    final ASN1OctetString value = control.getValue();
625    if (value == null)
626    {
627      throw new LDAPException(ResultCode.DECODING_ERROR,
628                              ERR_VLV_REQUEST_NO_VALUE.get());
629    }
630
631    try
632    {
633      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
634      final ASN1Element[] elements =
635           ASN1Sequence.decodeAsSequence(valueElement).elements();
636
637      beforeCount = ASN1Integer.decodeAsInteger(elements[0]).intValue();
638      afterCount  = ASN1Integer.decodeAsInteger(elements[1]).intValue();
639
640      switch (elements[2].getType())
641      {
642        case TARGET_TYPE_OFFSET:
643          assertionValue = null;
644          final ASN1Element[] offsetElements =
645               ASN1Sequence.decodeAsSequence(elements[2]).elements();
646          targetOffset =
647               ASN1Integer.decodeAsInteger(offsetElements[0]).intValue();
648          contentCount =
649               ASN1Integer.decodeAsInteger(offsetElements[1]).intValue();
650          break;
651
652        case TARGET_TYPE_GREATER_OR_EQUAL:
653          assertionValue = ASN1OctetString.decodeAsOctetString(elements[2]);
654          targetOffset   = -1;
655          contentCount   = -1;
656          break;
657
658        default:
659          throw new LDAPException(ResultCode.DECODING_ERROR,
660               ERR_VLV_REQUEST_INVALID_ELEMENT_TYPE.get(
661                    StaticUtils.toHex(elements[2].getType())));
662      }
663
664      if (elements.length == 4)
665      {
666        contextID = ASN1OctetString.decodeAsOctetString(elements[3]);
667      }
668      else
669      {
670        contextID = null;
671      }
672    }
673    catch (final LDAPException le)
674    {
675      Debug.debugException(le);
676      throw le;
677    }
678    catch (final Exception e)
679    {
680      Debug.debugException(e);
681      throw new LDAPException(ResultCode.DECODING_ERROR,
682           ERR_VLV_REQUEST_CANNOT_DECODE.get(e), e);
683    }
684  }
685
686
687
688  /**
689   * Encodes the provided information into an octet string that can be used as
690   * the value for this control.
691   *
692   * @param  targetOffset  The position of the entry that should be used as the
693   *                       start of the result set.
694   * @param  beforeCount   The maximum number of entries that should be returned
695   *                       before the entry with the specified target offset.
696   * @param  afterCount    The maximum number of entries that should be returned
697   *                       after the entry with the specified target offset.
698   * @param  contentCount  The estimated number of entries in the result set.
699   *                       For the first request in a series of searches with
700   *                       the VLV control, it should be zero.  For subsequent
701   *                       searches in the VLV sequence, it should be the
702   *                       content count included in the response control from
703   *                       the previous search.
704   * @param  contextID     The context ID that may be used to help the server
705   *                       continue in the same result set for subsequent
706   *                       searches.  For the first request in a series of
707   *                       searches with the VLV control, it should be
708   *                       {@code null}.  For subsequent searches in the VLV
709   *                       sequence, it should be the (possibly {@code null})
710   *                       context ID included in the response control from the
711   *                       previous search.
712   *
713   * @return  An ASN.1 octet string that can be used as the value for this
714   *          control.
715   */
716  @NotNull()
717  private static ASN1OctetString encodeValue(final int targetOffset,
718                      final int beforeCount, final int afterCount,
719                      final int contentCount,
720                      @Nullable final ASN1OctetString contextID)
721  {
722    final ASN1Element[] targetElements =
723    {
724      new ASN1Integer(targetOffset),
725      new ASN1Integer(contentCount)
726    };
727
728    final ASN1Element[] vlvElements;
729    if (contextID == null)
730    {
731      vlvElements = new ASN1Element[]
732      {
733        new ASN1Integer(beforeCount),
734        new ASN1Integer(afterCount),
735        new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements)
736      };
737    }
738    else
739    {
740      vlvElements = new ASN1Element[]
741      {
742        new ASN1Integer(beforeCount),
743        new ASN1Integer(afterCount),
744        new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements),
745        contextID
746      };
747    }
748
749    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
750  }
751
752
753
754  /**
755   * Encodes the provided information into an octet string that can be used as
756   * the value for this control.
757   *
758   * @param  assertionValue  The assertion value that will be used to identify
759   *                         the start of the result set.  The target entry will
760   *                         be the first entry with a value for the primary
761   *                         sort attribute that is greater than or equal to
762   *                         this assertion value.
763   * @param  beforeCount     The maximum number of entries that should be
764   *                         returned before the first entry with a value
765   *                         greater than or equal to the provided assertion
766   *                         value.
767   * @param  afterCount      The maximum number of entries that should be
768   *                         returned after the first entry with a value
769   *                         greater than or equal to the provided assertion
770   *                         value.
771   * @param  contextID       The context ID that may be used to help the server
772   *                         continue in the same result set for subsequent
773   *                         searches.  For the first request in a series of
774   *                         searches with the VLV control, it should be
775   *                         {@code null}.  For subsequent searches in the VLV
776   *                         sequence, it should be the (possibly {@code null})
777   *                         context ID included in the response control from
778   *                         the previous search.
779   *
780   * @return  An ASN.1 octet string that can be used as the value for this
781   *          control.
782   */
783  @NotNull()
784  private static ASN1OctetString encodeValue(
785                      @NotNull final ASN1OctetString assertionValue,
786                      final int beforeCount,
787                      final int afterCount,
788                      @Nullable final ASN1OctetString contextID)
789  {
790    Validator.ensureNotNull(assertionValue);
791
792    final ASN1Element[] vlvElements;
793    if (contextID == null)
794    {
795      vlvElements = new ASN1Element[]
796      {
797        new ASN1Integer(beforeCount),
798        new ASN1Integer(afterCount),
799        new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL,
800                            assertionValue.getValue())
801      };
802    }
803    else
804    {
805      vlvElements = new ASN1Element[]
806      {
807        new ASN1Integer(beforeCount),
808        new ASN1Integer(afterCount),
809        new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL,
810                            assertionValue.getValue()),
811        contextID
812      };
813    }
814
815    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
816  }
817
818
819
820  /**
821   * Retrieves the target offset position for this virtual list view request
822   * control, if applicable.
823   *
824   * @return  The target offset position for this virtual list view request
825   *          control, or -1 if the target is specified by an assertion value.
826   */
827  public int getTargetOffset()
828  {
829    return targetOffset;
830  }
831
832
833
834  /**
835   * Retrieves the string representation of the assertion value for this virtual
836   * list view request control, if applicable.
837   *
838   * @return  The string representation of the assertion value for this virtual
839   *          list view request control, or {@code null} if the target is
840   *          specified by offset.
841   */
842  @Nullable()
843  public String getAssertionValueString()
844  {
845    if (assertionValue == null)
846    {
847      return null;
848    }
849    else
850    {
851      return assertionValue.stringValue();
852    }
853  }
854
855
856
857  /**
858   * Retrieves the byte array representation of the assertion value for this
859   * virtual list view request control, if applicable.
860   *
861   * @return  The byte array representation of the assertion value for this
862   *          virtual list view request control, or {@code null} if the target
863   *          is specified by offset.
864   */
865  @Nullable()
866  public byte[] getAssertionValueBytes()
867  {
868    if (assertionValue == null)
869    {
870      return null;
871    }
872    else
873    {
874      return assertionValue.getValue();
875    }
876  }
877
878
879
880  /**
881   * Retrieves the assertion value for this virtual list view request control,
882   * if applicable.
883   *
884   * @return  The assertion value for this virtual list view request control, or
885   *          {@code null} if the target is specified by offset.
886   */
887  @Nullable()
888  public ASN1OctetString getAssertionValue()
889  {
890    return assertionValue;
891  }
892
893
894
895  /**
896   * Retrieves the number of entries that should be retrieved before the target
897   * entry.
898   *
899   * @return  The number of entries that should be retrieved before the target
900   *          entry.
901   */
902  public int getBeforeCount()
903  {
904    return beforeCount;
905  }
906
907
908
909  /**
910   * Retrieves the number of entries that should be retrieved after the target
911   * entry.
912   *
913   * @return  The number of entries that should be retrieved after the target
914   *          entry.
915   */
916  public int getAfterCount()
917  {
918    return afterCount;
919  }
920
921
922
923  /**
924   * Retrieves the estimated number of entries in the result set, if applicable.
925   *
926   * @return  The estimated number of entries in the result set, zero if it
927   *          is not known (for the first search in a sequence where the
928   *          target is specified by offset), or -1 if the target is specified
929   *          by an assertion value.
930   */
931  public int getContentCount()
932  {
933    return contentCount;
934  }
935
936
937
938  /**
939   * Retrieves the context ID for this virtual list view request control, if
940   * available.
941   *
942   * @return  The context ID for this virtual list view request control, or
943   *          {@code null} if there is none.
944   */
945  @Nullable()
946  public ASN1OctetString getContextID()
947  {
948    return contextID;
949  }
950
951
952
953  /**
954   * {@inheritDoc}
955   */
956  @Override()
957  @NotNull()
958  public String getControlName()
959  {
960    return INFO_CONTROL_NAME_VLV_REQUEST.get();
961  }
962
963
964
965  /**
966   * Retrieves a representation of this virtual list view request control as a
967   * JSON object.  The JSON object uses the following fields:
968   * <UL>
969   *   <LI>
970   *     {@code oid} -- A mandatory string field whose value is the object
971   *     identifier for this control.  For the virtual list view request
972   *     control, the OID is "2.16.840.1.113730.3.4.9".
973   *   </LI>
974   *   <LI>
975   *     {@code control-name} -- An optional string field whose value is a
976   *     human-readable name for this control.  This field is only intended for
977   *     descriptive purposes, and when decoding a control, the {@code oid}
978   *     field should be used to identify the type of control.
979   *   </LI>
980   *   <LI>
981   *     {@code criticality} -- A mandatory Boolean field used to indicate
982   *     whether this control is considered critical.
983   *   </LI>
984   *   <LI>
985   *     {@code value-base64} -- An optional string field whose value is a
986   *     base64-encoded representation of the raw value for this virtual list
987   *     view request control.  Exactly one of the {@code value-base64} and
988   *     {@code value-json} fields must be present.
989   *   </LI>
990   *   <LI>
991   *     {@code value-json} -- An optional JSON object field whose value is a
992   *     user-friendly representation of the value for this virtual list view
993   *     request control.  Exactly one of the {@code value-base64} and
994   *     {@code value-json} fields must be present, and if the
995   *     {@code value-json} field is used, then it will use the following
996   *     fields:
997   *     <UL>
998   *       <LI>
999   *         {@code target-offset} -- An optional integer field whose value is
1000   *         the offset of the target entry within the result set, with the
1001   *         first entry in the result set having an offset value of one.
1002   *         Exactly one of the {@code target-offset} and
1003   *         {@code assertion-value} fields must be provided.
1004   *       </LI>
1005   *       <LI>
1006   *         {@code assertion-value} -- An optional string field that indicates
1007   *         that the target entry should be the first one in the result set in
1008   *         which the value of the primary sort attribute is greater than or
1009   *         equal to the provided assertion value.  Exactly one of the
1010   *         {@code target-offset} and {@code assertion-value} fields must be
1011   *         provided.
1012   *       </LI>
1013   *       <LI>
1014   *         {@code before-count} -- A mandatory integer field whose value is
1015   *         the maximum number of entries before the target entry that should
1016   *         be included in the page of results to return.
1017   *       </LI>
1018   *       <LI>
1019   *         {@code after-count} -- A mandatory integer field whose value is
1020   *         the maximum number of entries after the target entry that should
1021   *         be included in the page of results to return.
1022   *       </LI>
1023   *       <LI>
1024   *         {@code content-count} -- An optional integer field that represents
1025   *         the estimated number of entries in the entire result set.  This
1026   *         field may only be present when the {@code target-offset} field is
1027   *         also provided, and its value may be absent or zero when retrieving
1028   *         the first page of results, and it should be the
1029   *         {@code content-count} value returned in the previous virtual list
1030   *         view response control for all subsequent pages.
1031   *       </LI>
1032   *       <LI>
1033   *         {@code context-id} -- An optional string field that represents an
1034   *         opaque cookie that may be used to help the server continue a series
1035   *         of searches using the virtual list view request control.  For the
1036   *         first search in a series, this should be absent.  For all
1037   *         subsequent requests in the series, it should be the
1038   *         {@code context-id} value (if any) included in the response control
1039   *         from the previous page of the series.  The context ID value used in
1040   *         the JSON representation of the control will be a base64-encoded
1041   *         representation of the raw cookie value that would be used in the
1042   *         LDAP representation of the control, and it must be treated as an
1043   *         opaque blob by the client.
1044   *       </LI>
1045   *     </UL>
1046   *   </LI>
1047   * </UL>
1048   *
1049   * @return  A JSON object that contains a representation of this control.
1050   */
1051  @Override()
1052  @NotNull()
1053  public JSONObject toJSONControl()
1054  {
1055    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
1056
1057    if (assertionValue == null)
1058    {
1059      valueFields.put(JSON_FIELD_TARGET_OFFSET, new JSONNumber(targetOffset));
1060    }
1061    else
1062    {
1063      valueFields.put(JSON_FIELD_ASSERTION_VALUE,
1064           new JSONString(assertionValue.stringValue()));
1065    }
1066
1067    valueFields.put(JSON_FIELD_BEFORE_COUNT, new JSONNumber(beforeCount));
1068    valueFields.put(JSON_FIELD_AFTER_COUNT, new JSONNumber(afterCount));
1069
1070    if (assertionValue == null)
1071    {
1072      valueFields.put(JSON_FIELD_CONTENT_COUNT, new JSONNumber(contentCount));
1073    }
1074
1075    if (contextID != null)
1076    {
1077      valueFields.put(JSON_FIELD_CONTEXT_ID,
1078           new JSONString(Base64.encode(contextID.getValue())));
1079    }
1080
1081    return new JSONObject(
1082         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
1083              VIRTUAL_LIST_VIEW_REQUEST_OID),
1084         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
1085              INFO_CONTROL_NAME_VLV_REQUEST.get()),
1086         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
1087              isCritical()),
1088         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
1089              new JSONObject(valueFields)));
1090  }
1091
1092
1093
1094  /**
1095   * Attempts to decode the provided object as a JSON representation of a
1096   * virtual list view request control.
1097   *
1098   * @param  controlObject  The JSON object to be decoded.  It must not be
1099   *                        {@code null}.
1100   * @param  strict         Indicates whether to use strict mode when decoding
1101   *                        the provided JSON object.  If this is {@code true},
1102   *                        then this method will throw an exception if the
1103   *                        provided JSON object contains any unrecognized
1104   *                        fields.  If this is {@code false}, then unrecognized
1105   *                        fields will be ignored.
1106   *
1107   * @return  The virtual list view request control that was decoded from the
1108   *          provided JSON object.
1109   *
1110   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1111   *                         valid virtual list view request control.
1112   */
1113  @NotNull()
1114  public static VirtualListViewRequestControl decodeJSONControl(
1115              @NotNull final JSONObject controlObject,
1116              final boolean strict)
1117         throws LDAPException
1118  {
1119    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1120         controlObject, strict, true, true);
1121
1122    final ASN1OctetString rawValue = jsonControl.getRawValue();
1123    if (rawValue != null)
1124    {
1125      return new VirtualListViewRequestControl(new Control(
1126           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
1127    }
1128
1129
1130    final JSONObject valueObject = jsonControl.getValueObject();
1131
1132    final Integer targetOffset =
1133         valueObject.getFieldAsInteger(JSON_FIELD_TARGET_OFFSET);
1134    final String assertionValue =
1135         valueObject.getFieldAsString(JSON_FIELD_ASSERTION_VALUE);
1136
1137    if ((targetOffset == null) && (assertionValue == null))
1138    {
1139      throw new LDAPException(ResultCode.DECODING_ERROR,
1140           ERR_VLV_REQUEST_JSON_NEITHER_OFFSET_NOR_VALUE.get(
1141                controlObject.toSingleLineString(), JSON_FIELD_TARGET_OFFSET,
1142                JSON_FIELD_ASSERTION_VALUE));
1143    }
1144
1145    if ((targetOffset != null) && (assertionValue != null))
1146    {
1147      throw new LDAPException(ResultCode.DECODING_ERROR,
1148           ERR_VLV_REQUEST_JSON_BOTH_OFFSET_AND_VALUE.get(
1149                controlObject.toSingleLineString(), JSON_FIELD_TARGET_OFFSET,
1150                JSON_FIELD_ASSERTION_VALUE));
1151    }
1152
1153
1154    final Integer beforeCount =
1155         valueObject.getFieldAsInteger(JSON_FIELD_BEFORE_COUNT);
1156    if (beforeCount == null)
1157    {
1158      throw new LDAPException(ResultCode.DECODING_ERROR,
1159           ERR_VLV_REQUEST_JSON_MISSING_FIELD.get(
1160                controlObject.toSingleLineString(), JSON_FIELD_BEFORE_COUNT));
1161    }
1162
1163
1164    final Integer afterCount =
1165         valueObject.getFieldAsInteger(JSON_FIELD_AFTER_COUNT);
1166    if (afterCount == null)
1167    {
1168      throw new LDAPException(ResultCode.DECODING_ERROR,
1169           ERR_VLV_REQUEST_JSON_MISSING_FIELD.get(
1170                controlObject.toSingleLineString(), JSON_FIELD_AFTER_COUNT));
1171    }
1172
1173
1174    Integer contentCount =
1175         valueObject.getFieldAsInteger(JSON_FIELD_CONTENT_COUNT);
1176    if (contentCount == null)
1177    {
1178      contentCount = 0;
1179    }
1180    else if (assertionValue != null)
1181    {
1182      throw new LDAPException(ResultCode.DECODING_ERROR,
1183           ERR_VLV_REQUEST_JSON_CONTENT_COUNT_WITH_ASSERTION_VALUE.get(
1184                controlObject.toSingleLineString(), JSON_FIELD_CONTENT_COUNT,
1185                JSON_FIELD_ASSERTION_VALUE));
1186    }
1187
1188
1189    final ASN1OctetString contextID;
1190    final String contextIDBase64 =
1191         valueObject.getFieldAsString(JSON_FIELD_CONTEXT_ID);
1192    if (contextIDBase64 == null)
1193    {
1194      contextID = null;
1195    }
1196    else
1197    {
1198      try
1199      {
1200        contextID = new ASN1OctetString(Base64.decode(contextIDBase64));
1201      }
1202      catch (final Exception e)
1203      {
1204        Debug.debugException(e);
1205        throw new LDAPException(ResultCode.DECODING_ERROR,
1206             ERR_VLV_REQUEST_JSON_CONTEXT_ID_NOT_BASE64.get(
1207                  controlObject.toSingleLineString(),
1208                  JSON_FIELD_CONTEXT_ID),
1209             e);
1210      }
1211    }
1212
1213
1214    if (strict)
1215    {
1216      final List<String> unrecognizedFields =
1217           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1218                valueObject, JSON_FIELD_TARGET_OFFSET,
1219                JSON_FIELD_ASSERTION_VALUE, JSON_FIELD_BEFORE_COUNT,
1220                JSON_FIELD_AFTER_COUNT, JSON_FIELD_CONTENT_COUNT,
1221                JSON_FIELD_CONTEXT_ID);
1222      if (! unrecognizedFields.isEmpty())
1223      {
1224        throw new LDAPException(ResultCode.DECODING_ERROR,
1225             ERR_VLV_REQUEST_JSON_UNRECOGNIZED_FIELD.get(
1226                  controlObject.toSingleLineString(),
1227                  unrecognizedFields.get(0)));
1228      }
1229    }
1230
1231
1232    if (assertionValue == null)
1233    {
1234      return new VirtualListViewRequestControl(targetOffset, beforeCount,
1235           afterCount, contentCount, contextID, jsonControl.getCriticality());
1236    }
1237    else
1238    {
1239      return new VirtualListViewRequestControl(assertionValue, beforeCount,
1240           afterCount, contextID, jsonControl.getCriticality());
1241    }
1242  }
1243
1244
1245
1246  /**
1247   * {@inheritDoc}
1248   */
1249  @Override()
1250  public void toString(@NotNull final StringBuilder buffer)
1251  {
1252    buffer.append("VirtualListViewRequestControl(beforeCount=");
1253    buffer.append(beforeCount);
1254    buffer.append(", afterCount=");
1255    buffer.append(afterCount);
1256
1257    if (assertionValue == null)
1258    {
1259      buffer.append(", targetOffset=");
1260      buffer.append(targetOffset);
1261      buffer.append(", contentCount=");
1262      buffer.append(contentCount);
1263    }
1264    else
1265    {
1266      buffer.append(", assertionValue='");
1267      buffer.append(assertionValue.stringValue());
1268      buffer.append('\'');
1269    }
1270
1271    buffer.append(", isCritical=");
1272    buffer.append(isCritical());
1273    buffer.append(')');
1274  }
1275}