001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2010 Sun Microsystems, Inc. 025 * Portions copyright 2012-2015 ForgeRock AS. 026 */ 027package org.forgerock.opendj.ldap.controls; 028 029import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; 030import static com.forgerock.opendj.ldap.CoreMessages.ERR_SORTRES_CONTROL_BAD_OID; 031import static com.forgerock.opendj.ldap.CoreMessages.INFO_SORTRES_CONTROL_CANNOT_DECODE_VALUE; 032import static com.forgerock.opendj.ldap.CoreMessages.INFO_SORTRES_CONTROL_NO_VALUE; 033 034import java.io.IOException; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.LocalizedIllegalArgumentException; 038import org.forgerock.opendj.io.ASN1; 039import org.forgerock.opendj.io.ASN1Reader; 040import org.forgerock.opendj.io.ASN1Writer; 041import org.forgerock.opendj.ldap.AttributeDescription; 042import org.forgerock.opendj.ldap.ByteString; 043import org.forgerock.opendj.ldap.ByteStringBuilder; 044import org.forgerock.opendj.ldap.DecodeException; 045import org.forgerock.opendj.ldap.DecodeOptions; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.forgerock.opendj.ldap.schema.Schema; 048import org.forgerock.util.Reject; 049 050/** 051 * The server-side sort response control as defined in RFC 2891. This control is 052 * included with a search result in response to a server-side sort request 053 * included with a search request. The client application is assured that the 054 * search results are sorted in the specified key order if and only if the 055 * result code in this control is success. If the server omits this control from 056 * the search result, the client SHOULD assume that the sort control was ignored 057 * by the server. 058 * <p> 059 * The following example demonstrates how to work with a server-side sort. 060 * 061 * <pre> 062 * Connection connection = ...; 063 * 064 * SearchRequest request = Requests.newSearchRequest( 065 * "ou=People,dc=example,dc=com", SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn") 066 * .addControl(ServerSideSortRequestControl.newControl(true, new SortKey("cn"))); 067 * 068 * SearchResultHandler resultHandler = new MySearchResultHandler(); 069 * Result result = connection.search(request, resultHandler); 070 * 071 * ServerSideSortResponseControl control = result.getControl( 072 * ServerSideSortResponseControl.DECODER, new DecodeOptions()); 073 * if (control != null && control.getResult() == ResultCode.SUCCESS) { 074 * // Entries are sorted. 075 * } else { 076 * // Entries not sorted. 077 * } 078 * </pre> 079 * 080 * @see ServerSideSortRequestControl 081 * @see <a href="http://tools.ietf.org/html/rfc2891">RFC 2891 - LDAP Control 082 * Extension for Server Side Sorting of Search Results </a> 083 */ 084public final class ServerSideSortResponseControl implements Control { 085 /** 086 * The OID for the server-side sort response control. 087 */ 088 public static final String OID = "1.2.840.113556.1.4.474"; 089 090 /** 091 * A decoder which can be used for decoding the server side sort response 092 * control. 093 */ 094 public static final ControlDecoder<ServerSideSortResponseControl> DECODER = 095 new ControlDecoder<ServerSideSortResponseControl>() { 096 097 public ServerSideSortResponseControl decodeControl(final Control control, 098 final DecodeOptions options) throws DecodeException { 099 Reject.ifNull(control, options); 100 101 if (control instanceof ServerSideSortResponseControl) { 102 return (ServerSideSortResponseControl) control; 103 } 104 105 if (!control.getOID().equals(OID)) { 106 final LocalizableMessage message = 107 ERR_SORTRES_CONTROL_BAD_OID.get(control.getOID(), OID); 108 throw DecodeException.error(message); 109 } 110 111 if (!control.hasValue()) { 112 // The request control must always have a value. 113 final LocalizableMessage message = INFO_SORTRES_CONTROL_NO_VALUE.get(); 114 throw DecodeException.error(message); 115 } 116 117 final ASN1Reader reader = ASN1.getReader(control.getValue()); 118 try { 119 reader.readStartSequence(); 120 121 // FIXME: should really check that result code is one of 122 // the expected 123 // values listed in the RFC. 124 final ResultCode result = ResultCode.valueOf(reader.readEnumerated()); 125 126 AttributeDescription attributeDescription = null; 127 if (reader.hasNextElement()) { 128 // FIXME: which schema should we use? 129 final Schema schema = options.getSchemaResolver().resolveSchema(""); 130 final String ads = reader.readOctetStringAsString(); 131 attributeDescription = AttributeDescription.valueOf(ads, schema); 132 } 133 134 return new ServerSideSortResponseControl(control.isCritical(), result, 135 attributeDescription); 136 } catch (final IOException | LocalizedIllegalArgumentException e) { 137 final LocalizableMessage message = 138 INFO_SORTRES_CONTROL_CANNOT_DECODE_VALUE.get(getExceptionMessage(e)); 139 throw DecodeException.error(message, e); 140 } 141 } 142 143 public String getOID() { 144 return OID; 145 } 146 }; 147 148 /** 149 * The BER type to use when encoding the attribute type element. 150 */ 151 private static final byte TYPE_ATTRIBUTE_TYPE = (byte) 0x80; 152 153 /** 154 * Creates a new server-side response control with the provided sort result 155 * and no attribute description. 156 * 157 * @param result 158 * The result code indicating the outcome of the server-side sort 159 * request. {@link ResultCode#SUCCESS} if the search results were 160 * sorted in accordance with the keys specified in the 161 * server-side sort request control, or an error code indicating 162 * why the results could not be sorted (such as 163 * {@link ResultCode#NO_SUCH_ATTRIBUTE} or 164 * {@link ResultCode#INAPPROPRIATE_MATCHING}). 165 * @return The new control. 166 * @throws NullPointerException 167 * If {@code result} was {@code null}. 168 */ 169 public static ServerSideSortResponseControl newControl(final ResultCode result) { 170 Reject.ifNull(result); 171 172 return new ServerSideSortResponseControl(false, result, null); 173 } 174 175 /** 176 * Creates a new server-side response control with the provided sort result 177 * and attribute description. 178 * 179 * @param result 180 * The result code indicating the outcome of the server-side sort 181 * request. {@link ResultCode#SUCCESS} if the search results were 182 * sorted in accordance with the keys specified in the 183 * server-side sort request control, or an error code indicating 184 * why the results could not be sorted (such as 185 * {@link ResultCode#NO_SUCH_ATTRIBUTE} or 186 * {@link ResultCode#INAPPROPRIATE_MATCHING}). 187 * @param attributeDescription 188 * The first attribute description specified in the list of sort 189 * keys that was in error, may be {@code null}. 190 * @return The new control. 191 * @throws NullPointerException 192 * If {@code result} was {@code null}. 193 */ 194 public static ServerSideSortResponseControl newControl(final ResultCode result, 195 final AttributeDescription attributeDescription) { 196 Reject.ifNull(result); 197 198 return new ServerSideSortResponseControl(false, result, attributeDescription); 199 } 200 201 /** 202 * Creates a new server-side response control with the provided sort result 203 * and attribute description. The attribute description will be decoded 204 * using the default schema. 205 * 206 * @param result 207 * The result code indicating the outcome of the server-side sort 208 * request. {@link ResultCode#SUCCESS} if the search results were 209 * sorted in accordance with the keys specified in the 210 * server-side sort request control, or an error code indicating 211 * why the results could not be sorted (such as 212 * {@link ResultCode#NO_SUCH_ATTRIBUTE} or 213 * {@link ResultCode#INAPPROPRIATE_MATCHING}). 214 * @param attributeDescription 215 * The first attribute description specified in the list of sort 216 * keys that was in error, may be {@code null}. 217 * @return The new control. 218 * @throws LocalizedIllegalArgumentException 219 * If {@code attributeDescription} could not be parsed using the 220 * default schema. 221 * @throws NullPointerException 222 * If {@code result} was {@code null}. 223 */ 224 public static ServerSideSortResponseControl newControl(final ResultCode result, 225 final String attributeDescription) { 226 Reject.ifNull(result); 227 228 if (attributeDescription != null) { 229 return new ServerSideSortResponseControl(false, result, AttributeDescription 230 .valueOf(attributeDescription)); 231 } else { 232 return new ServerSideSortResponseControl(false, result, null); 233 } 234 } 235 236 private final ResultCode result; 237 238 private final AttributeDescription attributeDescription; 239 240 private final boolean isCritical; 241 242 private ServerSideSortResponseControl(final boolean isCritical, final ResultCode result, 243 final AttributeDescription attributeDescription) { 244 this.isCritical = isCritical; 245 this.result = result; 246 this.attributeDescription = attributeDescription; 247 } 248 249 /** 250 * Returns the first attribute description specified in the list of sort 251 * keys that was in error, or {@code null} if the attribute description was 252 * not included with this control. 253 * 254 * @return The first attribute description specified in the list of sort 255 * keys that was in error. 256 */ 257 public AttributeDescription getAttributeDescription() { 258 return attributeDescription; 259 } 260 261 /** {@inheritDoc} */ 262 public String getOID() { 263 return OID; 264 } 265 266 /** 267 * Returns a result code indicating the outcome of the server-side sort 268 * request. This will be {@link ResultCode#SUCCESS} if the search results 269 * were sorted in accordance with the keys specified in the server-side sort 270 * request control, or an error code indicating why the results could not be 271 * sorted (such as {@link ResultCode#NO_SUCH_ATTRIBUTE} or 272 * {@link ResultCode#INAPPROPRIATE_MATCHING}). 273 * 274 * @return The result code indicating the outcome of the server-side sort 275 * request. 276 */ 277 public ResultCode getResult() { 278 return result; 279 } 280 281 /** {@inheritDoc} */ 282 public ByteString getValue() { 283 final ByteStringBuilder buffer = new ByteStringBuilder(); 284 final ASN1Writer writer = ASN1.getWriter(buffer); 285 try { 286 writer.writeStartSequence(); 287 writer.writeEnumerated(result.intValue()); 288 if (attributeDescription != null) { 289 writer.writeOctetString(TYPE_ATTRIBUTE_TYPE, attributeDescription.toString()); 290 } 291 writer.writeEndSequence(); 292 return buffer.toByteString(); 293 } catch (final IOException ioe) { 294 // This should never happen unless there is a bug somewhere. 295 throw new RuntimeException(ioe); 296 } 297 } 298 299 /** {@inheritDoc} */ 300 public boolean hasValue() { 301 return true; 302 } 303 304 /** {@inheritDoc} */ 305 public boolean isCritical() { 306 return isCritical; 307 } 308 309 /** {@inheritDoc} */ 310 @Override 311 public String toString() { 312 final StringBuilder builder = new StringBuilder(); 313 builder.append("ServerSideSortResponseControl(oid="); 314 builder.append(getOID()); 315 builder.append(", criticality="); 316 builder.append(isCritical()); 317 builder.append(", result="); 318 builder.append(result); 319 if (attributeDescription != null) { 320 builder.append(", attributeDescription="); 321 builder.append(attributeDescription); 322 } 323 builder.append(")"); 324 return builder.toString(); 325 } 326}