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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.controls; 028 import org.opends.messages.Message; 029 030 031 032 import java.util.ArrayList; 033 import java.util.StringTokenizer; 034 035 import org.opends.server.api.OrderingMatchingRule; 036 import org.opends.server.core.DirectoryServer; 037 import org.opends.server.protocols.asn1.ASN1Boolean; 038 import org.opends.server.protocols.asn1.ASN1Element; 039 import org.opends.server.protocols.asn1.ASN1OctetString; 040 import org.opends.server.protocols.asn1.ASN1Sequence; 041 import org.opends.server.protocols.ldap.LDAPResultCode; 042 import org.opends.server.types.AttributeType; 043 import org.opends.server.types.Control; 044 import org.opends.server.types.LDAPException; 045 import org.opends.server.types.SortKey; 046 import org.opends.server.types.SortOrder; 047 048 import static org.opends.messages.ProtocolMessages.*; 049 import static org.opends.server.util.ServerConstants.*; 050 import static org.opends.server.util.StaticUtils.*; 051 052 053 054 /** 055 * This class implements the server-side sort request control as defined in RFC 056 * 2891 section 1.1. The ASN.1 description for the control value is: 057 * <BR><BR> 058 * <PRE> 059 * SortKeyList ::= SEQUENCE OF SEQUENCE { 060 * attributeType AttributeDescription, 061 * orderingRule [0] MatchingRuleId OPTIONAL, 062 * reverseOrder [1] BOOLEAN DEFAULT FALSE } 063 * </PRE> 064 */ 065 public class ServerSideSortRequestControl 066 extends Control 067 { 068 /** 069 * The BER type to use when encoding the orderingRule element. 070 */ 071 private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80; 072 073 074 075 /** 076 * The BER type to use when encoding the reverseOrder element. 077 */ 078 private static final byte TYPE_REVERSE_ORDER = (byte) 0x81; 079 080 081 082 // The sort order associated with this control. 083 private SortOrder sortOrder; 084 085 086 087 /** 088 * Creates a new server-side sort request control based on the provided sort 089 * order. 090 * 091 * @param sortOrder The sort order to use for this control. 092 */ 093 public ServerSideSortRequestControl(SortOrder sortOrder) 094 { 095 super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, false, 096 encodeControlValue(sortOrder)); 097 098 this.sortOrder = sortOrder; 099 } 100 101 102 103 /** 104 * Creates a new server-side sort request control based on the definition in 105 * the provided sort order string. This is only intended for client-side use, 106 * and controls created with this constructor should not attempt to use the 107 * generated sort order for any purpose. 108 * 109 * @param sortOrderString The string representation of the sort order to use 110 * for the control. 111 * 112 * @throws LDAPException If the provided sort order string could not be 113 * decoded. 114 */ 115 public ServerSideSortRequestControl(String sortOrderString) 116 throws LDAPException 117 { 118 super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, false, 119 encodeControlValue(sortOrderString)); 120 121 this.sortOrder = null; 122 } 123 124 125 126 /** 127 * Creates a new server-side sort request control with the provided 128 * information. 129 * 130 * @param oid The OID to use for this control. 131 * @param isCritical Indicates whether support for this control should be 132 * considered a critical part of the server processing. 133 * @param controlValue The encoded value for this control. 134 * @param sortOrder sort order associated with this server-side sort 135 * control. 136 */ 137 private ServerSideSortRequestControl(String oid, boolean isCritical, 138 ASN1OctetString controlValue, 139 SortOrder sortOrder) 140 { 141 super(oid, isCritical, controlValue); 142 143 this.sortOrder = sortOrder; 144 } 145 146 147 148 /** 149 * Retrieves the sort order for this server-side sort request control. 150 * 151 * @return The sort order for this server-side sort request control. 152 */ 153 public SortOrder getSortOrder() 154 { 155 return sortOrder; 156 } 157 158 159 160 /** 161 * Encodes the provided sort order object in a manner suitable for use as the 162 * value of this control. 163 * 164 * @param sortOrder The sort order to be encoded. 165 * 166 * @return The ASN.1 octet string containing the encoded sort order. 167 */ 168 private static ASN1OctetString encodeControlValue(SortOrder sortOrder) 169 { 170 SortKey[] sortKeys = sortOrder.getSortKeys(); 171 ArrayList<ASN1Element> keyList = 172 new ArrayList<ASN1Element>(sortKeys.length); 173 for (SortKey sortKey : sortKeys) 174 { 175 ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3); 176 elementList.add(new ASN1OctetString( 177 sortKey.getAttributeType().getNameOrOID())); 178 179 if (sortKey.getOrderingRule() != null) 180 { 181 elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID, 182 sortKey.getOrderingRule().getNameOrOID())); 183 } 184 185 if (! sortKey.ascending()) 186 { 187 elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, true)); 188 } 189 190 keyList.add(new ASN1Sequence(elementList)); 191 } 192 193 return new ASN1OctetString(new ASN1Sequence(keyList).encode()); 194 } 195 196 197 198 /** 199 * Encodes the provided sort order string in a manner suitable for use as the 200 * value of this control. 201 * 202 * @param sortOrderString The sort order string to be encoded. 203 * 204 * @return The ASN.1 octet string containing the encoded sort order. 205 * 206 * @throws LDAPException If the provided sort order string cannot be decoded 207 * to create the control value. 208 */ 209 private static ASN1OctetString encodeControlValue(String sortOrderString) 210 throws LDAPException 211 { 212 StringTokenizer tokenizer = new StringTokenizer(sortOrderString, ","); 213 214 ArrayList<ASN1Element> keyList = new ArrayList<ASN1Element>(); 215 while (tokenizer.hasMoreTokens()) 216 { 217 String token = tokenizer.nextToken().trim(); 218 boolean reverseOrder = false; 219 if (token.startsWith("-")) 220 { 221 reverseOrder = true; 222 token = token.substring(1); 223 } 224 else if (token.startsWith("+")) 225 { 226 token = token.substring(1); 227 } 228 229 int colonPos = token.indexOf(':'); 230 if (colonPos < 0) 231 { 232 if (token.length() == 0) 233 { 234 Message message = 235 INFO_SORTREQ_CONTROL_NO_ATTR_NAME.get(sortOrderString); 236 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 237 } 238 239 if (reverseOrder) 240 { 241 ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(2); 242 elementList.add(new ASN1OctetString(token)); 243 elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder)); 244 keyList.add(new ASN1Sequence(elementList)); 245 } 246 else 247 { 248 ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(1); 249 elementList.add(new ASN1OctetString(token)); 250 keyList.add(new ASN1Sequence(elementList)); 251 } 252 } 253 else if (colonPos == 0) 254 { 255 Message message = 256 INFO_SORTREQ_CONTROL_NO_ATTR_NAME.get(sortOrderString); 257 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 258 } 259 else if (colonPos == (token.length() - 1)) 260 { 261 Message message = 262 INFO_SORTREQ_CONTROL_NO_MATCHING_RULE.get(sortOrderString); 263 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 264 } 265 else 266 { 267 String attrName = token.substring(0, colonPos); 268 String ruleID = token.substring(colonPos+1); 269 270 if (reverseOrder) 271 { 272 ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3); 273 elementList.add(new ASN1OctetString(attrName)); 274 elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID, ruleID)); 275 elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder)); 276 keyList.add(new ASN1Sequence(elementList)); 277 } 278 else 279 { 280 ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(2); 281 elementList.add(new ASN1OctetString(attrName)); 282 elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID, ruleID)); 283 keyList.add(new ASN1Sequence(elementList)); 284 } 285 } 286 } 287 288 if (keyList.isEmpty()) 289 { 290 Message message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get(); 291 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 292 } 293 294 return new ASN1OctetString(new ASN1Sequence(keyList).encode()); 295 } 296 297 298 299 /** 300 * Creates a new server-side sort request control from the contents of the 301 * provided control. 302 * 303 * @param control The generic control containing the information to use to 304 * create this server-side sort request control. It must not 305 * be {@code null}. 306 * 307 * @return The server-side sort request control decoded from the provided 308 * control. 309 * 310 * @throws LDAPException If this control cannot be decoded as a valid 311 * server-side sort request control. 312 */ 313 public static ServerSideSortRequestControl decodeControl(Control control) 314 throws LDAPException 315 { 316 ASN1OctetString controlValue = control.getValue(); 317 if (controlValue == null) 318 { 319 Message message = INFO_SORTREQ_CONTROL_NO_VALUE.get(); 320 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 321 } 322 323 try 324 { 325 ASN1Sequence orderSequence = 326 ASN1Sequence.decodeAsSequence(controlValue.value()); 327 ArrayList<ASN1Element> orderElements = orderSequence.elements(); 328 SortKey[] sortKeys = new SortKey[orderElements.size()]; 329 if (sortKeys.length == 0) 330 { 331 Message message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get(); 332 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 333 } 334 335 for (int i=0; i < sortKeys.length; i++) 336 { 337 ASN1Sequence keySequence = orderElements.get(i).decodeAsSequence(); 338 ArrayList<ASN1Element> keyElements = keySequence.elements(); 339 340 String attrName = 341 keyElements.get(0).decodeAsOctetString().stringValue(). 342 toLowerCase(); 343 AttributeType attrType = DirectoryServer.getAttributeType(attrName, 344 false); 345 if (attrType == null) 346 { 347 Message message = INFO_SORTREQ_CONTROL_UNDEFINED_ATTR.get(attrName); 348 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 349 } 350 351 OrderingMatchingRule orderingRule = null; 352 boolean ascending = true; 353 354 for (int j=1; j < keyElements.size(); j++) 355 { 356 ASN1Element e = keyElements.get(j); 357 switch (e.getType()) 358 { 359 case TYPE_ORDERING_RULE_ID: 360 String orderingRuleID = 361 e.decodeAsOctetString().stringValue().toLowerCase(); 362 orderingRule = 363 DirectoryServer.getOrderingMatchingRule(orderingRuleID); 364 if (orderingRule == null) 365 { 366 Message message = INFO_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE. 367 get(orderingRuleID); 368 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 369 } 370 break; 371 372 case TYPE_REVERSE_ORDER: 373 ascending = ! e.decodeAsBoolean().booleanValue(); 374 break; 375 376 default: 377 Message message = INFO_SORTREQ_CONTROL_INVALID_SEQ_ELEMENT_TYPE. 378 get(byteToHex(e.getType())); 379 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 380 } 381 } 382 383 if ((orderingRule == null) && 384 (attrType.getOrderingMatchingRule() == null)) 385 { 386 Message message = 387 INFO_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR.get(attrName); 388 throw new LDAPException(LDAPResultCode.CONSTRAINT_VIOLATION, message); 389 } 390 391 sortKeys[i] = new SortKey(attrType, ascending, orderingRule); 392 } 393 394 return new ServerSideSortRequestControl(control.getOID(), 395 control.isCritical(), 396 controlValue, 397 new SortOrder(sortKeys)); 398 } 399 catch (LDAPException le) 400 { 401 throw le; 402 } 403 catch (Exception e) 404 { 405 Message message = 406 INFO_SORTREQ_CONTROL_CANNOT_DECODE_VALUE.get(getExceptionMessage(e)); 407 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e); 408 } 409 } 410 411 412 413 /** 414 * Retrieves a string representation of this server-side sort request control. 415 * 416 * @return A string representation of this server-side sort request control. 417 */ 418 public String toString() 419 { 420 StringBuilder buffer = new StringBuilder(); 421 toString(buffer); 422 return buffer.toString(); 423 } 424 425 426 427 /** 428 * Appends a string representation of this server-side sort request control 429 * to the provided buffer. 430 * 431 * @param buffer The buffer to which the information should be appended. 432 */ 433 public void toString(StringBuilder buffer) 434 { 435 buffer.append("ServerSideSortRequestControl("); 436 437 if (sortOrder != null) 438 { 439 buffer.append(sortOrder); 440 } 441 442 buffer.append(")"); 443 } 444 } 445