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