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 */ 021package com.unboundid.ldap.sdk.controls; 022 023 024 025import java.util.List; 026 027import com.unboundid.asn1.ASN1Element; 028import com.unboundid.asn1.ASN1OctetString; 029import com.unboundid.asn1.ASN1Sequence; 030import com.unboundid.ldap.sdk.Control; 031import com.unboundid.ldap.sdk.LDAPException; 032import com.unboundid.ldap.sdk.ResultCode; 033import com.unboundid.util.NotMutable; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036 037import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 038import static com.unboundid.util.Debug.*; 039import 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) 134public 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}