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