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 2008-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 */ 017package org.opends.server.controls; 018 019import static org.opends.messages.ProtocolMessages.*; 020import static org.opends.server.util.ServerConstants.*; 021import static org.opends.server.util.StaticUtils.*; 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.StringTokenizer; 027 028import org.forgerock.i18n.LocalizableMessage; 029import org.forgerock.opendj.io.ASN1; 030import org.forgerock.opendj.io.ASN1Reader; 031import org.forgerock.opendj.io.ASN1Writer; 032import org.forgerock.opendj.ldap.AttributeDescription; 033import org.forgerock.opendj.ldap.ByteString; 034import org.forgerock.opendj.ldap.ResultCode; 035import org.forgerock.opendj.ldap.SortKey; 036import org.forgerock.opendj.ldap.schema.AttributeType; 037import org.forgerock.opendj.ldap.schema.MatchingRule; 038import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; 039import org.opends.server.core.DirectoryServer; 040import org.opends.server.protocols.ldap.LDAPResultCode; 041import org.opends.server.types.Control; 042import org.opends.server.types.DirectoryException; 043import org.opends.server.types.LDAPException; 044 045/** 046 * This class implements the server-side sort request control as defined in RFC 047 * 2891 section 1.1. The subclass ServerSideSortRequestControl.ClientRequest 048 * should be used when encoding this control from a sort order string. This is 049 * suitable for client tools that want to encode this control without a 050 * SortOrder object. The ASN.1 description for the control value is: 051 * <BR><BR> 052 * <PRE> 053 * SortKeyList ::= SEQUENCE OF SEQUENCE { 054 * attributeType AttributeDescription, 055 * orderingRule [0] MatchingRuleId OPTIONAL, 056 * reverseOrder [1] BOOLEAN DEFAULT FALSE } 057 * </PRE> 058 */ 059public class ServerSideSortRequestControl 060 extends Control 061{ 062 /** The BER type to use when encoding the orderingRule element. */ 063 private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80; 064 065 066 067 /** The BER type to use when encoding the reverseOrder element. */ 068 private static final byte TYPE_REVERSE_ORDER = (byte) 0x81; 069 070 071 /** ControlDecoder implementation to decode this control from a ByteString. */ 072 private static final class Decoder 073 implements ControlDecoder<ServerSideSortRequestControl> 074 { 075 @Override 076 public ServerSideSortRequestControl decode(boolean isCritical, 077 ByteString value) 078 throws DirectoryException 079 { 080 if (value == null) 081 { 082 LocalizableMessage message = INFO_SORTREQ_CONTROL_NO_VALUE.get(); 083 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 084 } 085 086 ASN1Reader reader = ASN1.getReader(value); 087 try 088 { 089 reader.readStartSequence(); 090 if (!reader.hasNextElement()) 091 { 092 LocalizableMessage message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get(); 093 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 094 } 095 096 ArrayList<SortKey> sortKeys = new ArrayList<>(); 097 while(reader.hasNextElement()) 098 { 099 reader.readStartSequence(); 100 String attrName = reader.readOctetStringAsString(); 101 AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrName); 102 if (attrType.isPlaceHolder()) 103 { 104 //This attribute is not defined in the schema. There is no point 105 //iterating over the next attribute and return a partially sorted result. 106 return new ServerSideSortRequestControl(isCritical, sortKeys); 107 } 108 109 MatchingRule orderingRule = null; 110 boolean isReverseOrder = false; 111 if(reader.hasNextElement() && 112 reader.peekType() == TYPE_ORDERING_RULE_ID) 113 { 114 String orderingRuleID = reader.readOctetStringAsString(); 115 try 116 { 117 orderingRule = DirectoryServer.getSchema().getMatchingRule(orderingRuleID); 118 } 119 catch (UnknownSchemaElementException e) 120 { 121 LocalizableMessage message = INFO_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE.get(orderingRuleID); 122 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 123 } 124 } 125 if(reader.hasNextElement() && 126 reader.peekType() == TYPE_REVERSE_ORDER) 127 { 128 isReverseOrder = reader.readBoolean(); 129 } 130 reader.readEndSequence(); 131 132 if (orderingRule == null && attrType.getOrderingMatchingRule() == null) 133 { 134 LocalizableMessage message = 135 INFO_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR.get(attrName); 136 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 137 } 138 139 sortKeys.add(new SortKey(AttributeDescription.create(attrType), isReverseOrder, orderingRule)); 140 } 141 reader.readEndSequence(); 142 143 return new ServerSideSortRequestControl(isCritical, sortKeys); 144 } 145 catch (DirectoryException de) 146 { 147 throw de; 148 } 149 catch (Exception e) 150 { 151 LocalizableMessage message = 152 INFO_SORTREQ_CONTROL_CANNOT_DECODE_VALUE.get( 153 getExceptionMessage(e)); 154 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e); 155 } 156 } 157 158 @Override 159 public String getOID() 160 { 161 return OID_SERVER_SIDE_SORT_REQUEST_CONTROL; 162 } 163 164 } 165 166 /** The Control Decoder that can be used to decode this control. */ 167 public static final ControlDecoder<ServerSideSortRequestControl> DECODER = 168 new Decoder(); 169 170 /** The sort order associated with this control represented by strings. */ 171 private List<String[]> decodedKeyList; 172 /** The sort order associated with this control. */ 173 private List<SortKey> sortKeys; 174 175 /** 176 * Creates a new server-side sort request control based on the definition in 177 * the provided sort order string. 178 * 179 * @param sortOrderString The string representation of the sort order to 180 * use for the control. 181 * @throws LDAPException If the provided sort order string could not be 182 * decoded. 183 */ 184 public ServerSideSortRequestControl(String sortOrderString) 185 throws LDAPException 186 { 187 this(false, sortOrderString); 188 } 189 190 /** 191 * Creates a new server-side sort request control based on the definition in 192 * the provided sort order string. 193 * 194 * @param isCritical Indicates whether support for this control 195 * should be considered a critical part of the 196 * server processing. 197 * @param sortOrderString The string representation of the sort order to 198 * use for the control. 199 * @throws LDAPException If the provided sort order string could not be 200 * decoded. 201 */ 202 public ServerSideSortRequestControl(boolean isCritical, 203 String sortOrderString) 204 throws LDAPException 205 { 206 super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical); 207 208 StringTokenizer tokenizer = new StringTokenizer(sortOrderString, ","); 209 210 decodedKeyList = new ArrayList<>(); 211 while (tokenizer.hasMoreTokens()) 212 { 213 String token = tokenizer.nextToken().trim(); 214 boolean reverseOrder = false; 215 if (token.startsWith("-")) 216 { 217 reverseOrder = true; 218 token = token.substring(1); 219 } 220 else if (token.startsWith("+")) 221 { 222 token = token.substring(1); 223 } 224 225 int colonPos = token.indexOf(':'); 226 if (colonPos < 0) 227 { 228 if (token.length() == 0) 229 { 230 LocalizableMessage message = 231 INFO_SORTREQ_CONTROL_NO_ATTR_NAME.get(sortOrderString); 232 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 233 } 234 235 if (reverseOrder) 236 { 237 decodedKeyList.add(new String[]{token, null, "r"}); 238 } 239 else 240 { 241 decodedKeyList.add(new String[]{token, null, null}); 242 } 243 } 244 else if (colonPos == 0) 245 { 246 LocalizableMessage message = 247 INFO_SORTREQ_CONTROL_NO_ATTR_NAME.get(sortOrderString); 248 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 249 } 250 else if (colonPos == (token.length() - 1)) 251 { 252 LocalizableMessage message = 253 INFO_SORTREQ_CONTROL_NO_MATCHING_RULE.get(sortOrderString); 254 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 255 } 256 else 257 { 258 String attrName = token.substring(0, colonPos); 259 String ruleID = token.substring(colonPos+1); 260 261 if (reverseOrder) 262 { 263 decodedKeyList.add(new String[]{attrName, ruleID, "r"}); 264 } 265 else 266 { 267 decodedKeyList.add(new String[]{attrName, ruleID, null}); 268 } 269 } 270 } 271 272 if (decodedKeyList.isEmpty()) 273 { 274 LocalizableMessage message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get(); 275 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 276 } 277 } 278 279 280 /** 281 * Creates a new server-side sort request control based on the provided sort 282 * order. 283 * 284 * @param sortKeys The sort order to use for this control. 285 */ 286 public ServerSideSortRequestControl(List<SortKey> sortKeys) 287 { 288 this(false, sortKeys); 289 } 290 291 /** 292 * Creates a new server-side sort request control with the provided 293 * information. 294 * 295 * @param isCritical Indicates whether support for this control should be 296 * considered a critical part of the server processing. 297 * @param sortKeys sort order associated with this server-side sort 298 * control. 299 */ 300 public ServerSideSortRequestControl(boolean isCritical, List<SortKey> sortKeys) 301 { 302 super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical); 303 304 this.sortKeys = sortKeys; 305 } 306 307 308 /** 309 * Retrieves the sort order for this server-side sort request control. 310 * 311 * @return The sort order for this server-side sort request control. 312 * @throws DirectoryException if an error occurs while retrieving the sort order. 313 */ 314 public List<SortKey> getSortKeys() throws DirectoryException 315 { 316 if (sortKeys == null) 317 { 318 sortKeys = decodeSortKeysFromString(); 319 } 320 return sortKeys; 321 } 322 323 /** 324 * Indicates whether the sort control contains Sort keys. 325 * 326 * <P> A Sort control may not contain sort keys if the attribute type 327 * is not recognized by the server </P> 328 * 329 * @return <CODE>true</CODE> if the control contains sort keys 330 * or <CODE>false</CODE> if it does not. 331 * 332 * @throws DirectoryException If a problem occurs while trying to make the 333 * determination. 334 */ 335 public boolean containsSortKeys() throws DirectoryException 336 { 337 return !getSortKeys().isEmpty(); 338 } 339 340 /** 341 * Writes this control's value to an ASN.1 writer. The value (if any) must 342 * be written as an ASN1OctetString. 343 * 344 * @param writer The ASN.1 writer to use. 345 * @throws IOException If a problem occurs while writing to the stream. 346 347 */ 348 @Override 349 protected void writeValue(ASN1Writer writer) throws IOException { 350 if(decodedKeyList != null) 351 { 352 // This control was created with a sort order string so encode using 353 // that. 354 writeValueFromString(writer); 355 } 356 else 357 { 358 // This control must have been created with a typed sort order object 359 // so encode using that. 360 writeValueFromSortOrder(writer); 361 } 362 } 363 364 /** 365 * Appends a string representation of this server-side sort request control 366 * to the provided buffer. 367 * 368 * @param buffer The buffer to which the information should be appended. 369 */ 370 @Override 371 public void toString(StringBuilder buffer) 372 { 373 buffer.append("ServerSideSortRequestControl("); 374 if (sortKeys == null) 375 { 376 buffer.append("SortOrder("); 377 378 if (!decodedKeyList.isEmpty()) 379 { 380 decodedKeyToString(decodedKeyList.get(0), buffer); 381 382 for (int i=1; i < decodedKeyList.size(); i++) 383 { 384 buffer.append(","); 385 decodedKeyToString(decodedKeyList.get(i), buffer); 386 } 387 } 388 buffer.append(")"); 389 } 390 else 391 { 392 buffer.append(sortKeys); 393 } 394 buffer.append(")"); 395 } 396 397 private void decodedKeyToString(String[] decodedKey, StringBuilder buffer) 398 { 399 buffer.append("SortKey("); 400 if (decodedKey[2] == null) 401 { 402 buffer.append("+"); 403 } 404 else 405 { 406 buffer.append("-"); 407 } 408 buffer.append(decodedKey[0]); 409 410 if (decodedKey[1] != null) 411 { 412 buffer.append(":"); 413 buffer.append(decodedKey[1]); 414 } 415 416 buffer.append(")"); 417 } 418 419 private List<SortKey> decodeSortKeysFromString() throws DirectoryException 420 { 421 List<SortKey> sortKeys = new ArrayList<>(); 422 for(String[] decodedKey : decodedKeyList) 423 { 424 AttributeType attrType = DirectoryServer.getSchema().getAttributeType(decodedKey[0]); 425 if (attrType.isPlaceHolder()) 426 { 427 //This attribute is not defined in the schema. There is no point 428 //iterating over the next attribute and return a partially sorted result. 429 return sortKeys; 430 } 431 432 MatchingRule orderingRule = null; 433 if(decodedKey[1] != null) 434 { 435 try 436 { 437 orderingRule = DirectoryServer.getSchema().getMatchingRule(decodedKey[1]); 438 } 439 catch (UnknownSchemaElementException e) 440 { 441 LocalizableMessage message = INFO_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE.get(decodedKey[1]); 442 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 443 } 444 } 445 446 String decodedKey2 = decodedKey[2]; 447 boolean isReverseOrder = decodedKey2 != null && decodedKey2.equals("r"); 448 if (orderingRule == null 449 && attrType.getOrderingMatchingRule() == null) 450 { 451 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 452 INFO_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR.get(decodedKey[0])); 453 } 454 455 sortKeys.add(new SortKey(AttributeDescription.create(attrType), isReverseOrder, orderingRule)); 456 } 457 458 return sortKeys; 459 } 460 461 private void writeValueFromString(ASN1Writer writer) throws IOException 462 { 463 writer.writeStartSequence(ASN1.UNIVERSAL_OCTET_STRING_TYPE); 464 465 writer.writeStartSequence(); 466 for(String[] strs : decodedKeyList) 467 { 468 writer.writeStartSequence(); 469 // Attr name will always be present 470 writer.writeOctetString(strs[0]); 471 // Rule ID might not be present 472 if(strs[1] != null) 473 { 474 writer.writeOctetString(TYPE_ORDERING_RULE_ID, strs[1]); 475 } 476 // Reverse if present 477 if(strs[2] != null) 478 { 479 writer.writeBoolean(TYPE_REVERSE_ORDER, true); 480 } 481 writer.writeEndSequence(); 482 } 483 writer.writeEndSequence(); 484 485 writer.writeEndSequence(); 486 } 487 488 private void writeValueFromSortOrder(ASN1Writer writer) throws IOException 489 { 490 writer.writeStartSequence(ASN1.UNIVERSAL_OCTET_STRING_TYPE); 491 492 writer.writeStartSequence(); 493 for (SortKey sortKey : sortKeys) 494 { 495 writer.writeStartSequence(); 496 writer.writeOctetString(sortKey.getAttributeDescription()); 497 498 if (sortKey.getOrderingMatchingRule() != null) 499 { 500 writer.writeOctetString(TYPE_ORDERING_RULE_ID, sortKey.getOrderingMatchingRule()); 501 } 502 503 if (sortKey.isReverseOrder()) 504 { 505 writer.writeBoolean(TYPE_REVERSE_ORDER, true); 506 } 507 508 writer.writeEndSequence(); 509 } 510 writer.writeEndSequence(); 511 512 writer.writeEndSequence(); 513 } 514} 515