001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010 Sun Microsystems, Inc. 015 * Portions copyright 2012-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.controls; 018 019import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; 020import static com.forgerock.opendj.ldap.CoreMessages.*; 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.StringTokenizer; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.LocalizedIllegalArgumentException; 033import org.forgerock.opendj.io.ASN1; 034import org.forgerock.opendj.io.ASN1Reader; 035import org.forgerock.opendj.io.ASN1Writer; 036import org.forgerock.opendj.ldap.ByteString; 037import org.forgerock.opendj.ldap.ByteStringBuilder; 038import org.forgerock.opendj.ldap.DecodeException; 039import org.forgerock.opendj.ldap.DecodeOptions; 040import org.forgerock.opendj.ldap.SortKey; 041import org.forgerock.util.Reject; 042 043/** 044 * The server-side sort request control as defined in RFC 2891. This control may 045 * be included in a search request to indicate that search result entries should 046 * be sorted by the server before being returned. The sort order is specified 047 * using one or more sort keys, the first being the primary key, and so on. 048 * <p> 049 * This controls may be useful when the client has limited functionality or for 050 * some other reason cannot sort the results but still needs them sorted. In 051 * cases where the client can sort the results client-side sorting is 052 * recommended in order to reduce load on the server. See {@link SortKey} for an 053 * example of client-side sorting. 054 * <p> 055 * The following example demonstrates how to work with a server-side sort. 056 * 057 * <pre> 058 * Connection connection = ...; 059 * 060 * SearchRequest request = Requests.newSearchRequest( 061 * "ou=People,dc=example,dc=com", SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn") 062 * .addControl(ServerSideSortRequestControl.newControl(true, new SortKey("cn"))); 063 * 064 * SearchResultHandler resultHandler = new MySearchResultHandler(); 065 * Result result = connection.search(request, resultHandler); 066 * 067 * ServerSideSortResponseControl control = result.getControl( 068 * ServerSideSortResponseControl.DECODER, new DecodeOptions()); 069 * if (control != null && control.getResult() == ResultCode.SUCCESS) { 070 * // Entries are sorted. 071 * } else { 072 * // Entries not sorted. 073 * } 074 * </pre> 075 * 076 * @see ServerSideSortResponseControl 077 * @see SortKey 078 * @see <a href="http://tools.ietf.org/html/rfc2891">RFC 2891 - LDAP Control 079 * Extension for Server Side Sorting of Search Results </a> 080 */ 081public final class ServerSideSortRequestControl implements Control { 082 /** The OID for the server-side sort request control. */ 083 public static final String OID = "1.2.840.113556.1.4.473"; 084 085 /** The BER type to use when encoding the orderingRule element. */ 086 private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80; 087 088 /** The BER type to use when encoding the reverseOrder element. */ 089 private static final byte TYPE_REVERSE_ORDER = (byte) 0x81; 090 091 /** A decoder which can be used for decoding the server side sort request control. */ 092 public static final ControlDecoder<ServerSideSortRequestControl> DECODER = 093 new ControlDecoder<ServerSideSortRequestControl>() { 094 095 @Override 096 public ServerSideSortRequestControl decodeControl(final Control control, 097 final DecodeOptions options) throws DecodeException { 098 Reject.ifNull(control); 099 100 if (control instanceof ServerSideSortRequestControl) { 101 return (ServerSideSortRequestControl) control; 102 } 103 104 if (!control.getOID().equals(OID)) { 105 final LocalizableMessage message = 106 ERR_SORTREQ_CONTROL_BAD_OID.get(control.getOID(), OID); 107 throw DecodeException.error(message); 108 } 109 110 if (!control.hasValue()) { 111 // The request control must always have a value. 112 final LocalizableMessage message = INFO_SORTREQ_CONTROL_NO_VALUE.get(); 113 throw DecodeException.error(message); 114 } 115 116 final ASN1Reader reader = ASN1.getReader(control.getValue()); 117 try { 118 reader.readStartSequence(); 119 if (!reader.hasNextElement()) { 120 final LocalizableMessage message = 121 INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get(); 122 throw DecodeException.error(message); 123 } 124 125 final List<SortKey> keys = new LinkedList<>(); 126 while (reader.hasNextElement()) { 127 reader.readStartSequence(); 128 final String attrName = reader.readOctetStringAsString(); 129 130 String orderingRule = null; 131 boolean reverseOrder = false; 132 if (reader.hasNextElement() 133 && (reader.peekType() == TYPE_ORDERING_RULE_ID)) { 134 orderingRule = reader.readOctetStringAsString(); 135 } 136 if (reader.hasNextElement() 137 && (reader.peekType() == TYPE_REVERSE_ORDER)) { 138 reverseOrder = reader.readBoolean(); 139 } 140 reader.readEndSequence(); 141 142 keys.add(new SortKey(attrName, reverseOrder, orderingRule)); 143 } 144 reader.readEndSequence(); 145 146 return new ServerSideSortRequestControl(control.isCritical(), Collections 147 .unmodifiableList(keys)); 148 } catch (final IOException e) { 149 final LocalizableMessage message = 150 INFO_SORTREQ_CONTROL_CANNOT_DECODE_VALUE 151 .get(getExceptionMessage(e)); 152 throw DecodeException.error(message, e); 153 } 154 } 155 156 @Override 157 public String getOID() { 158 return OID; 159 } 160 }; 161 162 /** 163 * Creates a new server side sort request control with the provided 164 * criticality and list of sort keys. 165 * 166 * @param isCritical 167 * {@code true} if it is unacceptable to perform the operation 168 * without applying the semantics of this control, or 169 * {@code false} if it can be ignored. 170 * @param keys 171 * The list of sort keys. 172 * @return The new control. 173 * @throws IllegalArgumentException 174 * If {@code keys} was empty. 175 * @throws NullPointerException 176 * If {@code keys} was {@code null}. 177 */ 178 public static ServerSideSortRequestControl newControl(final boolean isCritical, 179 final Collection<SortKey> keys) { 180 Reject.ifNull(keys); 181 Reject.ifFalse(!keys.isEmpty(), "keys must not be empty"); 182 183 return new ServerSideSortRequestControl(isCritical, Collections 184 .unmodifiableList(new ArrayList<SortKey>(keys))); 185 } 186 187 /** 188 * Creates a new server side sort request control with the provided 189 * criticality and list of sort keys. 190 * 191 * @param isCritical 192 * {@code true} if it is unacceptable to perform the operation 193 * without applying the semantics of this control, or 194 * {@code false} if it can be ignored. 195 * @param keys 196 * The list of sort keys. 197 * @return The new control. 198 * @throws IllegalArgumentException 199 * If {@code keys} was empty. 200 * @throws NullPointerException 201 * If {@code keys} was {@code null}. 202 */ 203 public static ServerSideSortRequestControl newControl(final boolean isCritical, 204 final SortKey... keys) { 205 return newControl(isCritical, Arrays.asList(keys)); 206 } 207 208 /** 209 * Creates a new server side sort request control with the provided 210 * criticality and string representation of a list of sort keys. The string 211 * representation is comprised of a comma separate list of sort keys as 212 * defined in {@link SortKey#valueOf(String)}. There must be at least one 213 * sort key present in the string representation. 214 * 215 * @param isCritical 216 * {@code true} if it is unacceptable to perform the operation 217 * without applying the semantics of this control, or 218 * {@code false} if it can be ignored. 219 * @param sortKeys 220 * The list of sort keys. 221 * @return The new control. 222 * @throws LocalizedIllegalArgumentException 223 * If {@code sortKeys} is not a valid string representation of a 224 * list of sort keys. 225 * @throws NullPointerException 226 * If {@code sortKeys} was {@code null}. 227 */ 228 public static ServerSideSortRequestControl newControl(final boolean isCritical, 229 final String sortKeys) { 230 Reject.ifNull(sortKeys); 231 232 final List<SortKey> keys = new LinkedList<>(); 233 final StringTokenizer tokenizer = new StringTokenizer(sortKeys, ","); 234 while (tokenizer.hasMoreTokens()) { 235 final String token = tokenizer.nextToken().trim(); 236 keys.add(SortKey.valueOf(token)); 237 } 238 if (keys.isEmpty()) { 239 final LocalizableMessage message = ERR_SORT_KEY_NO_SORT_KEYS.get(sortKeys); 240 throw new LocalizedIllegalArgumentException(message); 241 } 242 return new ServerSideSortRequestControl(isCritical, Collections.unmodifiableList(keys)); 243 } 244 245 private final List<SortKey> sortKeys; 246 247 private final boolean isCritical; 248 249 private ServerSideSortRequestControl(final boolean isCritical, final List<SortKey> keys) { 250 this.isCritical = isCritical; 251 this.sortKeys = keys; 252 } 253 254 @Override 255 public String getOID() { 256 return OID; 257 } 258 259 /** 260 * Returns an unmodifiable list containing the sort keys associated with 261 * this server side sort request control. The list will contain at least one 262 * sort key. 263 * 264 * @return An unmodifiable list containing the sort keys associated with 265 * this server side sort request control. 266 */ 267 public List<SortKey> getSortKeys() { 268 return sortKeys; 269 } 270 271 @Override 272 public ByteString getValue() { 273 final ByteStringBuilder buffer = new ByteStringBuilder(); 274 final ASN1Writer writer = ASN1.getWriter(buffer); 275 try { 276 writer.writeStartSequence(); 277 for (final SortKey sortKey : sortKeys) { 278 writer.writeStartSequence(); 279 writer.writeOctetString(sortKey.getAttributeDescription()); 280 281 if (sortKey.getOrderingMatchingRule() != null) { 282 writer.writeOctetString(TYPE_ORDERING_RULE_ID, sortKey 283 .getOrderingMatchingRule()); 284 } 285 286 if (sortKey.isReverseOrder()) { 287 writer.writeBoolean(TYPE_REVERSE_ORDER, true); 288 } 289 290 writer.writeEndSequence(); 291 } 292 writer.writeEndSequence(); 293 return buffer.toByteString(); 294 } catch (final IOException ioe) { 295 // This should never happen unless there is a bug somewhere. 296 throw new RuntimeException(ioe); 297 } 298 } 299 300 @Override 301 public boolean hasValue() { 302 return true; 303 } 304 305 @Override 306 public boolean isCritical() { 307 return isCritical; 308 } 309 310 @Override 311 public String toString() { 312 final StringBuilder buffer = new StringBuilder(); 313 buffer.append("ServerSideSortRequestControl(oid="); 314 buffer.append(getOID()); 315 buffer.append(", criticality="); 316 buffer.append(isCritical()); 317 buffer.append(", sortKeys="); 318 buffer.append(sortKeys); 319 buffer.append(")"); 320 return buffer.toString(); 321 } 322 323}