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