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}