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