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 java.util.List;
041
042import com.unboundid.asn1.ASN1Element;
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.asn1.ASN1Sequence;
045import com.unboundid.ldap.sdk.Control;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053import com.unboundid.util.Validator;
054
055import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
056
057
058
059/**
060 * This class provides an implementation of the server-side sort request
061 * control, as defined in
062 * <A HREF="http://www.ietf.org/rfc/rfc2891.txt">RFC 2891</A>.  It may be
063 * included in a search request to indicate that the server should sort the
064 * results before returning them to the client.
065 * <BR><BR>
066 * The order in which the entries are to be sorted is specified by one or more
067 * {@link SortKey} values.  Each sort key includes an attribute name and a flag
068 * that indicates whether to sort in ascending or descending order.  It may also
069 * specify a custom matching rule that should be used to specify which logic
070 * should be used to perform the sorting.
071 * <BR><BR>
072 * If the search is successful, then the search result done message may include
073 * the {@link ServerSideSortResponseControl} to provide information about the
074 * status of the sort processing.
075 * <BR><BR>
076 * <H2>Example</H2>
077 * The following example demonstrates the use of the server-side sort controls
078 * to retrieve users in different sort orders.
079 * <PRE>
080 * // Perform a search to get all user entries sorted by last name, then by
081 * // first name, both in ascending order.
082 * SearchRequest searchRequest = new SearchRequest(
083 *      "ou=People,dc=example,dc=com", SearchScope.SUB,
084 *      Filter.createEqualityFilter("objectClass", "person"));
085 * searchRequest.addControl(new ServerSideSortRequestControl(
086 *      new SortKey("sn"), new SortKey("givenName")));
087 * SearchResult lastNameAscendingResult;
088 * try
089 * {
090 *   lastNameAscendingResult = connection.search(searchRequest);
091 *   // If we got here, then the search was successful.
092 * }
093 * catch (LDAPSearchException lse)
094 * {
095 *   // The search failed for some reason.
096 *   lastNameAscendingResult = lse.getSearchResult();
097 *   ResultCode resultCode = lse.getResultCode();
098 *   String errorMessageFromServer = lse.getDiagnosticMessage();
099 * }
100 *
101 * // Get the response control and retrieve the result code for the sort
102 * // processing.
103 * LDAPTestUtils.assertHasControl(lastNameAscendingResult,
104 *      ServerSideSortResponseControl.SERVER_SIDE_SORT_RESPONSE_OID);
105 * ServerSideSortResponseControl lastNameAscendingResponseControl =
106 *      ServerSideSortResponseControl.get(lastNameAscendingResult);
107 * ResultCode lastNameSortResult =
108 *      lastNameAscendingResponseControl.getResultCode();
109 *
110 *
111 * // Perform the same search, but this time request the results to be sorted
112 * // in descending order by first name, then last name.
113 * searchRequest.setControls(new ServerSideSortRequestControl(
114 *      new SortKey("givenName", true), new SortKey("sn", true)));
115 * SearchResult firstNameDescendingResult;
116 * try
117 * {
118 *   firstNameDescendingResult = connection.search(searchRequest);
119 *   // If we got here, then the search was successful.
120 * }
121 * catch (LDAPSearchException lse)
122 * {
123 *   // The search failed for some reason.
124 *   firstNameDescendingResult = lse.getSearchResult();
125 *   ResultCode resultCode = lse.getResultCode();
126 *   String errorMessageFromServer = lse.getDiagnosticMessage();
127 * }
128 *
129 * // Get the response control and retrieve the result code for the sort
130 * // processing.
131 * LDAPTestUtils.assertHasControl(firstNameDescendingResult,
132 *      ServerSideSortResponseControl.SERVER_SIDE_SORT_RESPONSE_OID);
133 * ServerSideSortResponseControl firstNameDescendingResponseControl =
134 *      ServerSideSortResponseControl.get(firstNameDescendingResult);
135 * ResultCode firstNameSortResult =
136 *      firstNameDescendingResponseControl.getResultCode();
137 * </PRE>
138 * <BR><BR>
139 * <H2>Client-Side Sorting</H2>
140 * The UnboundID LDAP SDK for Java provides support for client-side sorting as
141 * an alternative to server-side sorting.  Client-side sorting may be useful in
142 * cases in which the target server does not support the use of the server-side
143 * sort control, or when it is desirable to perform the sort processing on the
144 * client systems rather than on the directory server systems.  See the
145 * {@link com.unboundid.ldap.sdk.EntrySorter} class for details on performing
146 * client-side sorting in the LDAP SDK.
147 */
148@NotMutable()
149@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
150public final class ServerSideSortRequestControl
151       extends Control
152{
153  /**
154   * The OID (1.2.840.113556.1.4.473) for the server-side sort request control.
155   */
156  @NotNull public static final String SERVER_SIDE_SORT_REQUEST_OID =
157       "1.2.840.113556.1.4.473";
158
159
160
161  /**
162   * The serial version UID for this serializable class.
163   */
164  private static final long serialVersionUID = -3021901578330574772L;
165
166
167
168  // The set of sort keys to use with this control.
169  @NotNull private final SortKey[] sortKeys;
170
171
172
173  /**
174   * Creates a new server-side sort control that will sort the results based on
175   * the provided set of sort keys.
176   *
177   * @param  sortKeys  The set of sort keys to define the desired order in which
178   *                   the results should be returned.  It must not be
179   *                   {@code null} or empty.
180   */
181  public ServerSideSortRequestControl(@NotNull final SortKey... sortKeys)
182  {
183    this(false, sortKeys);
184  }
185
186
187
188  /**
189   * Creates a new server-side sort control that will sort the results based on
190   * the provided set of sort keys.
191   *
192   * @param  sortKeys  The set of sort keys to define the desired order in which
193   *                   the results should be returned.  It must not be
194   *                   {@code null} or empty.
195   */
196  public ServerSideSortRequestControl(@NotNull final List<SortKey> sortKeys)
197  {
198    this(false, sortKeys);
199  }
200
201
202
203  /**
204   * Creates a new server-side sort control that will sort the results based on
205   * the provided set of sort keys.
206   *
207   * @param  isCritical  Indicates whether this control should be marked
208   *                     critical.
209   * @param  sortKeys    The set of sort keys to define the desired order in
210   *                     which the results should be returned.  It must not be
211   *                     {@code null} or empty.
212   */
213  public ServerSideSortRequestControl(final boolean isCritical,
214                                      @NotNull final SortKey... sortKeys)
215  {
216    super(SERVER_SIDE_SORT_REQUEST_OID, isCritical, encodeValue(sortKeys));
217
218    this.sortKeys = sortKeys;
219  }
220
221
222
223  /**
224   * Creates a new server-side sort control that will sort the results based on
225   * the provided set of sort keys.
226   *
227   * @param  isCritical  Indicates whether this control should be marked
228   *                     critical.
229   * @param  sortKeys    The set of sort keys to define the desired order in
230   *                     which the results should be returned.  It must not be
231   *                     {@code null} or empty.
232   */
233  public ServerSideSortRequestControl(final boolean isCritical,
234                                      @NotNull final List<SortKey> sortKeys)
235  {
236    this(isCritical, sortKeys.toArray(new SortKey[sortKeys.size()]));
237  }
238
239
240
241  /**
242   * Creates a new server-side sort request control which is decoded from the
243   * provided generic control.
244   *
245   * @param  control  The generic control to be decoded as a server-side sort
246   *                  request control.
247   *
248   * @throws  LDAPException  If the provided control cannot be decoded as a
249   *                         server-side sort request control.
250   */
251  public ServerSideSortRequestControl(@NotNull final Control control)
252         throws LDAPException
253  {
254    super(control);
255
256    final ASN1OctetString value = control.getValue();
257    if (value == null)
258    {
259      throw new LDAPException(ResultCode.DECODING_ERROR,
260                              ERR_SORT_REQUEST_NO_VALUE.get());
261    }
262
263    try
264    {
265      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
266      final ASN1Element[] elements =
267           ASN1Sequence.decodeAsSequence(valueElement).elements();
268      sortKeys = new SortKey[elements.length];
269      for (int i=0; i < elements.length; i++)
270      {
271        sortKeys[i] = SortKey.decode(elements[i]);
272      }
273    }
274    catch (final Exception e)
275    {
276      Debug.debugException(e);
277      throw new LDAPException(ResultCode.DECODING_ERROR,
278                              ERR_SORT_REQUEST_CANNOT_DECODE.get(e), e);
279    }
280  }
281
282
283
284  /**
285   * Encodes the provided information into an octet string that can be used as
286   * the value for this control.
287   *
288   * @param  sortKeys  The set of sort keys to define the desired order in which
289   *                   the results should be returned.  It must not be
290   *                   {@code null} or empty.
291   *
292   * @return  An ASN.1 octet string that can be used as the value for this
293   *          control.
294   */
295  @NotNull()
296  private static ASN1OctetString encodeValue(@NotNull final SortKey[] sortKeys)
297  {
298    Validator.ensureNotNull(sortKeys);
299    Validator.ensureTrue(sortKeys.length > 0,
300         "ServerSideSortRequestControl.sortKeys must not be empty.");
301
302    final ASN1Element[] valueElements = new ASN1Element[sortKeys.length];
303    for (int i=0; i < sortKeys.length; i++)
304    {
305      valueElements[i] = sortKeys[i].encode();
306    }
307
308    return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
309  }
310
311
312
313  /**
314   * Retrieves the set of sort keys that define the desired order in which the
315   * results should be returned.
316   *
317   * @return  The set of sort keys that define the desired order in which the
318   *          results should be returned.
319   */
320  @NotNull()
321  public SortKey[] getSortKeys()
322  {
323    return sortKeys;
324  }
325
326
327
328  /**
329   * {@inheritDoc}
330   */
331  @Override()
332  @NotNull()
333  public String getControlName()
334  {
335    return INFO_CONTROL_NAME_SORT_REQUEST.get();
336  }
337
338
339
340  /**
341   * {@inheritDoc}
342   */
343  @Override()
344  public void toString(@NotNull final StringBuilder buffer)
345  {
346    buffer.append("ServerSideSortRequestControl(sortKeys={");
347
348    for (int i=0; i < sortKeys.length; i++)
349    {
350      if (i > 0)
351      {
352        buffer.append(", ");
353      }
354
355      buffer.append('\'');
356      sortKeys[i].toString(buffer);
357      buffer.append('\'');
358    }
359
360    buffer.append("})");
361  }
362}