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