001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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 com.unboundid.asn1.ASN1Element;
041import com.unboundid.asn1.ASN1Enumerated;
042import com.unboundid.asn1.ASN1Exception;
043import com.unboundid.asn1.ASN1Integer;
044import com.unboundid.asn1.ASN1OctetString;
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.DecodeableControl;
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.ResultCode;
050import com.unboundid.ldap.sdk.SearchResult;
051import com.unboundid.util.Debug;
052import com.unboundid.util.NotMutable;
053import com.unboundid.util.NotNull;
054import com.unboundid.util.Nullable;
055import com.unboundid.util.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057
058import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
059
060
061
062/**
063 * This class provides an implementation of the virtual list view (VLV) response
064 * control, as defined in draft-ietf-ldapext-ldapv3-vlv.  It may be used to
065 * provide information about the result of virtual list view processing for a
066 * search containing the {@link VirtualListViewRequestControl}.
067 * <BR><BR>
068 * The virtual list view response control may include the following elements:
069 * <UL>
070 *   <LI>{@code resultCode} -- A result code that indicates the result of the
071 *       virtual list view processing.  It may be the same as or different from
072 *       the result code contained in the search result done message.</LI>
073 *   <LI>{@code targetPosition} -- The offset of the target entry specified by
074 *       the client in the result set.</LI>
075 *   <LI>{@code contentCount} -- The estimated total number of entries in the
076 *       entire result set.</LI>
077 *   <LI>{@code contextID} -- An optional cookie that the client should include
078 *       in the next request as part of the virtual list view sequence.</LI>
079 * </UL>
080 */
081@NotMutable()
082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
083public final class VirtualListViewResponseControl
084       extends Control
085       implements DecodeableControl
086{
087  /**
088   * The OID (2.16.840.1.113730.3.4.10) for the virtual list view response
089   * control.
090   */
091  @NotNull public static final String VIRTUAL_LIST_VIEW_RESPONSE_OID =
092       "2.16.840.1.113730.3.4.10";
093
094
095
096  /**
097   * The serial version UID for this serializable class.
098   */
099  private static final long serialVersionUID = -534656674756287217L;
100
101
102
103  // The context ID for this VLV response control, if available.
104  @Nullable private final ASN1OctetString contextID;
105
106  // The estimated total number of entries in the result set.
107  private final int contentCount;
108
109  // The result code for this VLV response control.
110  @NotNull private final ResultCode resultCode;
111
112  // The offset of the target entry for this VLV response control.
113  private final int targetPosition;
114
115
116
117  /**
118   * Creates a new empty control instance that is intended to be used only for
119   * decoding controls via the {@code DecodeableControl} interface.
120   */
121  VirtualListViewResponseControl()
122  {
123    targetPosition = -1;
124    contentCount   = -1;
125    resultCode     = null;
126    contextID      = null;
127  }
128
129
130
131  /**
132   * Creates a new virtual list view response control with the provided
133   * information.  It will not be marked critical.
134   *
135   * @param  targetPosition  The offset of the target entry for this VLV
136   *                         response control.
137   * @param  contentCount    The estimated total number of entries in the
138   *                         result set.
139   * @param  resultCode      The result code for this VLV response control.
140   * @param  contextID       The context ID for this VLV response control.  It
141   *                         may be {@code null} if no context ID is available.
142   */
143  public VirtualListViewResponseControl(final int targetPosition,
144              final int contentCount, @NotNull final ResultCode resultCode,
145              @Nullable final ASN1OctetString contextID)
146  {
147    super(VIRTUAL_LIST_VIEW_RESPONSE_OID, false,
148          encodeValue(targetPosition, contentCount, resultCode, contextID));
149
150    this.targetPosition = targetPosition;
151    this.contentCount   = contentCount;
152    this.resultCode     = resultCode;
153    this.contextID      = contextID;
154  }
155
156
157
158  /**
159   * Creates a new virtual list view response control from the information
160   * contained in the provided control.
161   *
162   * @param  oid         The OID for the control.
163   * @param  isCritical  Indicates whether the control should be marked
164   *                     critical.
165   * @param  value       The encoded value for the control.  This may be
166   *                     {@code null} if no value was provided.
167   *
168   * @throws  LDAPException  If a problem occurs while attempting to decode the
169   *                         provided control as a virtual list view response
170   *                         control.
171   */
172  public VirtualListViewResponseControl(@NotNull final String oid,
173                                        final boolean isCritical,
174                                        @Nullable final ASN1OctetString value)
175         throws LDAPException
176  {
177    super(oid, isCritical, value);
178
179    if (value == null)
180    {
181      throw new LDAPException(ResultCode.DECODING_ERROR,
182                              ERR_VLV_RESPONSE_NO_VALUE.get());
183    }
184
185    final ASN1Sequence valueSequence;
186    try
187    {
188      final ASN1Element valueElement =
189           ASN1Element.decode(value.getValue());
190      valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
191    }
192    catch (final ASN1Exception ae)
193    {
194      Debug.debugException(ae);
195      throw new LDAPException(ResultCode.DECODING_ERROR,
196                              ERR_VLV_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae);
197    }
198
199    final ASN1Element[] valueElements = valueSequence.elements();
200    if ((valueElements.length < 3) || (valueElements.length > 4))
201    {
202      throw new LDAPException(ResultCode.DECODING_ERROR,
203                              ERR_VLV_RESPONSE_INVALID_ELEMENT_COUNT.get(
204                                   valueElements.length));
205    }
206
207    try
208    {
209      targetPosition = ASN1Integer.decodeAsInteger(valueElements[0]).intValue();
210    }
211    catch (final ASN1Exception ae)
212    {
213      Debug.debugException(ae);
214      throw new LDAPException(ResultCode.DECODING_ERROR,
215                              ERR_VLV_RESPONSE_FIRST_NOT_INTEGER.get(ae), ae);
216    }
217
218    try
219    {
220      contentCount = ASN1Integer.decodeAsInteger(valueElements[1]).intValue();
221    }
222    catch (final ASN1Exception ae)
223    {
224      Debug.debugException(ae);
225      throw new LDAPException(ResultCode.DECODING_ERROR,
226                              ERR_VLV_RESPONSE_SECOND_NOT_INTEGER.get(ae), ae);
227    }
228
229    try
230    {
231      final int rc =
232           ASN1Enumerated.decodeAsEnumerated(valueElements[2]).intValue();
233      resultCode = ResultCode.valueOf(rc);
234    }
235    catch (final ASN1Exception ae)
236    {
237      Debug.debugException(ae);
238      throw new LDAPException(ResultCode.DECODING_ERROR,
239                              ERR_VLV_RESPONSE_THIRD_NOT_ENUM.get(ae), ae);
240    }
241
242    if (valueElements.length == 4)
243    {
244      contextID = ASN1OctetString.decodeAsOctetString(valueElements[3]);
245    }
246    else
247    {
248      contextID = null;
249    }
250  }
251
252
253
254  /**
255   * {@inheritDoc}
256   */
257  @Override()
258  @NotNull()
259  public VirtualListViewResponseControl decodeControl(@NotNull final String oid,
260              final boolean isCritical,
261              @Nullable final ASN1OctetString value)
262         throws LDAPException
263  {
264    return new VirtualListViewResponseControl(oid, isCritical, value);
265  }
266
267
268
269  /**
270   * Extracts a virtual list view response control from the provided result.
271   *
272   * @param  result  The result from which to retrieve the virtual list view
273   *                 response control.
274   *
275   * @return  The virtual list view response  control contained in the provided
276   *          result, or {@code null} if the result did not contain a virtual
277   *          list view response control.
278   *
279   * @throws  LDAPException  If a problem is encountered while attempting to
280   *                         decode the virtual list view response  control
281   *                         contained in the provided result.
282   */
283  @Nullable()
284  public static VirtualListViewResponseControl get(
285                     @NotNull final SearchResult result)
286         throws LDAPException
287  {
288    final Control c = result.getResponseControl(VIRTUAL_LIST_VIEW_RESPONSE_OID);
289    if (c == null)
290    {
291      return null;
292    }
293
294    if (c instanceof VirtualListViewResponseControl)
295    {
296      return (VirtualListViewResponseControl) c;
297    }
298    else
299    {
300      return new VirtualListViewResponseControl(c.getOID(), c.isCritical(),
301           c.getValue());
302    }
303  }
304
305
306
307  /**
308   * Encodes the provided information into an octet string that can be used as
309   * the value for this control.
310   *
311   * @param  targetPosition  The offset of the target entry for this VLV
312   *                         response control.
313   * @param  contentCount    The estimated total number of entries in the
314   *                         result set.
315   * @param  resultCode      The result code for this VLV response control.
316   * @param  contextID       The context ID for this VLV response control.  It
317   *                         may be {@code null} if no context ID is available.
318   *
319   * @return  An ASN.1 octet string that can be used as the value for this
320   *          control.
321   */
322  @NotNull()
323  private static ASN1OctetString encodeValue(final int targetPosition,
324                      final int contentCount,
325                      @NotNull final ResultCode resultCode,
326                      @Nullable final ASN1OctetString contextID)
327  {
328    final ASN1Element[] vlvElements;
329    if (contextID == null)
330    {
331      vlvElements = new ASN1Element[]
332      {
333        new ASN1Integer(targetPosition),
334        new ASN1Integer(contentCount),
335        new ASN1Enumerated(resultCode.intValue())
336      };
337    }
338    else
339    {
340      vlvElements = new ASN1Element[]
341      {
342        new ASN1Integer(targetPosition),
343        new ASN1Integer(contentCount),
344        new ASN1Enumerated(resultCode.intValue()),
345        contextID
346      };
347    }
348
349    return new ASN1OctetString(new ASN1Sequence(vlvElements).encode());
350  }
351
352
353
354  /**
355   * Retrieves the offset of the target entry for this virtual list view
356   * response control.
357   *
358   * @return  The offset of the target entry for this virtual list view response
359   *          control.
360   */
361  public int getTargetPosition()
362  {
363    return targetPosition;
364  }
365
366
367
368  /**
369   * Retrieves the estimated total number of entries in the result set.
370   *
371   * @return  The estimated total number of entries in the result set.
372   */
373  public int getContentCount()
374  {
375    return contentCount;
376  }
377
378
379
380  /**
381   * Retrieves the result code for this virtual list view response control.
382   *
383   * @return  The result code for this virtual list view response control.
384   */
385  @NotNull()
386  public ResultCode getResultCode()
387  {
388    return resultCode;
389  }
390
391
392
393  /**
394   * Retrieves the context ID for this virtual list view response control, if
395   * available.
396   *
397   * @return  The context ID for this virtual list view response control, or
398   *          {@code null} if none was provided.
399   */
400  @Nullable()
401  public ASN1OctetString getContextID()
402  {
403    return contextID;
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  @NotNull()
413  public String getControlName()
414  {
415    return INFO_CONTROL_NAME_VLV_RESPONSE.get();
416  }
417
418
419
420  /**
421   * {@inheritDoc}
422   */
423  @Override()
424  public void toString(@NotNull final StringBuilder buffer)
425  {
426    buffer.append("VirtualListViewResponseControl(targetPosition=");
427    buffer.append(targetPosition);
428    buffer.append(", contentCount=");
429    buffer.append(contentCount);
430    buffer.append(", resultCode=");
431    buffer.append(resultCode);
432    buffer.append(')');
433  }
434}