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