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