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