mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
27.43.2007 172ae4c6e63be576376b32a68c8e8218f202fa22
Add initial support for server-side sorting in OpenDS.  This implementation
will only work for indexed searches, and it operates by sorting the ID list
before iterating through the entries to return them to the client.

The ldapsearch tool has also been updated to support the "-S" option to
specify that the entries should be sorted with a given sort order.

A more complete sorting solution, which will offer the ability to sort results
that are not otherwise indexed, will require a sort/VLV index mechanism like
that described in issue #38.

OpenDS Issue Number: 79
7 files added
11 files modified
3251 ■■■■■ changed files
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 5 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 79 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java 120 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IDSetIterator.java 31 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/SortValues.java 216 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/controls/ServerSideSortRequestControl.java 456 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/controls/ServerSideSortResponseControl.java 277 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 73 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/JebMessages.java 25 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ProtocolMessages.java 159 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ToolMessages.java 23 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/LDAPSearch.java 25 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/SortKey.java 278 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/SortOrder.java 135 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 16 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java 730 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java 581 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -55,9 +55,7 @@
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.*;
import static org.opends.server.util.ServerConstants.OID_SUBTREE_DELETE_CONTROL;
import static org.opends.server.util.ServerConstants.OID_PAGED_RESULTS_CONTROL;
import static org.opends.server.util.ServerConstants.OID_MANAGE_DSAIT_CONTROL;
import static org.opends.server.util.ServerConstants.*;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.std.meta.JEBackendCfgDefn;
import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -112,6 +110,7 @@
    supportedControls.add(OID_SUBTREE_DELETE_CONTROL);
    supportedControls.add(OID_PAGED_RESULTS_CONTROL);
    supportedControls.add(OID_MANAGE_DSAIT_CONTROL);
    supportedControls.add(OID_SERVER_SIDE_SORT_REQUEST_CONTROL);
  }
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -38,7 +38,10 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.controls.PagedResultsControl;
import org.opends.server.controls.ServerSideSortRequestControl;
import org.opends.server.controls.ServerSideSortResponseControl;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -60,6 +63,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -72,8 +76,7 @@
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugJEAccess;
import static org.opends.server.loggers.debug.DebugLogger.debugVerbose;
import static org.opends.server.util.ServerConstants.OID_SUBTREE_DELETE_CONTROL;
import static org.opends.server.util.ServerConstants.OID_PAGED_RESULTS_CONTROL;
import static org.opends.server.util.ServerConstants.*;
/**
 * Storage container for LDAP entries.  Each base DN of a JE backend is given
@@ -548,6 +551,7 @@
    List<Control> controls = searchOperation.getRequestControls();
    PagedResultsControl pageRequest = null;
    ServerSideSortRequestControl sortRequest = null;
    if (controls != null)
    {
      for (Control control : controls)
@@ -573,6 +577,26 @@
            }
          }
        }
        else if (control.getOID().equals(OID_SERVER_SIDE_SORT_REQUEST_CONTROL))
        {
          // Ignore all but the first sort request control.
          if (sortRequest == null)
          {
            try
            {
              sortRequest = ServerSideSortRequestControl.decodeControl(control);
            }
            catch (LDAPException e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                                           e.getMessage(), e.getMessageID(), e);
            }
          }
        }
      }
    }
@@ -729,11 +753,52 @@
    if (entryIDList.isDefined())
    {
      if (sortRequest != null)
      {
        try
        {
          entryIDList = EntryIDSetSorter.sort(this, entryIDList,
                                              searchOperation,
                                              sortRequest.getSortOrder());
          searchOperation.addResponseControl(
               new ServerSideSortResponseControl(LDAPResultCode.SUCCESS, null));
        }
        catch (DirectoryException de)
        {
          searchOperation.addResponseControl(
               new ServerSideSortResponseControl(
                        de.getResultCode().getIntValue(), null));
          if (sortRequest.isCritical())
          {
            throw de;
          }
        }
      }
      searchIndexed(entryIDList, candidatesAreInScope, searchOperation,
                    pageRequest);
    }
    else
    {
      if (sortRequest != null)
      {
        // FIXME -- Add support for sorting unindexed searches using indexes
        //          like DSEE currently does.
        searchOperation.addResponseControl(
             new ServerSideSortResponseControl(
                      LDAPResultCode.UNWILLING_TO_PERFORM, null));
        if (sortRequest.isCritical())
        {
          int msgID = MSGID_JEB_SEARCH_CANNOT_SORT_UNINDEXED;
          String message = getMessage(msgID);
          throw new DirectoryException(
                         ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, message,
                         msgID);
        }
      }
      searchNotIndexed(searchOperation, pageRequest);
    }
  }
@@ -1107,17 +1172,13 @@
    if (continueSearch)
    {
      List<Lock> lockList = new ArrayList<Lock>();
      for (EntryID id : entryIDList)
      Iterator<EntryID> iterator = entryIDList.iterator(begin);
      while (iterator.hasNext())
      {
        EntryID id = iterator.next();
        Entry entry = null;
        Entry cacheEntry = null;
        // Skip entry IDs in pages already returned.
        if (begin != null && id.compareTo(begin) < 0)
        {
          continue;
        }
        // Try the entry cache first. Note no need to take a lock.
        lockList.clear();
        cacheEntry = entryCache.getEntry(backend, id.longValue(),
opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
@@ -612,4 +612,26 @@
    }
  }
  /**
   * Create an iterator over the set or an empty iterator
   * if the set is not defined.
   *
   * @param  begin  The entry ID of the first entry to return in the list.
   *
   * @return An EntryID iterator.
   */
  public Iterator<EntryID> iterator(EntryID begin)
  {
    if (values == null)
    {
      // The set is not defined.
      return new IDSetIterator(new long[0]);
    }
    else
    {
      // The set is defined.
      return new IDSetIterator(values, begin);
    }
  }
}
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
New file
@@ -0,0 +1,120 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import java.util.TreeMap;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SortOrder;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides a mechanism for sorting the contents of an entry ID set
 * based on a given sort order.
 */
public class EntryIDSetSorter
{
  /**
   * Creates a new entry ID set which is a sorted representation of the provided
   * set using the given sort order.
   *
   * @param  entryContainer   The entry container with which the ID list is
   *                          associated.
   * @param  entryIDSet       The entry ID set to be sorted.
   * @param  searchOperation  The search operation being processed.
   * @param  sortOrder        The sort order to use for the entry ID set.
   *
   * @return  A new entry ID set which is a sorted representation of the
   *          provided set using the given sort order.
   *
   * @throws  DirectoryException  If an error occurs while performing the sort.
   */
  public static EntryIDSet sort(EntryContainer entryContainer,
                                EntryIDSet entryIDSet,
                                SearchOperation searchOperation,
                                SortOrder sortOrder)
         throws DirectoryException
  {
    if (! entryIDSet.isDefined())
    {
      return new EntryIDSet();
    }
    ID2Entry id2Entry = entryContainer.getID2Entry();
    DN baseDN = searchOperation.getBaseDN();
    SearchScope scope = searchOperation.getScope();
    SearchFilter filter = searchOperation.getFilter();
    TreeMap<SortValues,EntryID> sortMap = new TreeMap<SortValues,EntryID>();
    for (EntryID id : entryIDSet)
    {
      try
      {
        Entry e = id2Entry.get(null, id);
        if ((! e.matchesBaseAndScope(baseDN, scope)) ||
            (! filter.matchesEntry(e)))
        {
          continue;
        }
        sortMap.put(new SortValues(id, e, sortOrder), id);
      }
      catch (Exception e)
      {
        int msgID = MSGID_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY;
        String message = getMessage(msgID, String.valueOf(id),
                                    getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, msgID, e);
      }
    }
    long[] sortedIDs = new long[sortMap.size()];
    int i=0;
    for (EntryID id : sortMap.values())
    {
      sortedIDs[i++] = id.longValue();
    }
    return new EntryIDSet(sortedIDs, 0, sortedIDs.length);
  }
}
opends/src/server/org/opends/server/backends/jeb/IDSetIterator.java
@@ -54,6 +54,37 @@
  }
  /**
   * Create a new iterator for a given array of entry IDs.
   * @param entryIDList An array of IDs in order or ID.
   * @param begin The entry ID of the first entry that should be returned, or
   *              {@code null} if it should start at the beginning of the list.
   */
  public IDSetIterator(long[] entryIDList, EntryID begin)
  {
    this.entryIDList = entryIDList;
    if (begin == null)
    {
      i = 0;
    }
    else
    {
      for (i=0; i < entryIDList.length; i++)
      {
        if (entryIDList[i] == begin.longValue())
        {
          break;
        }
      }
      if (i >= entryIDList.length)
      {
        i = 0;
      }
    }
  }
  /**
   * Returns <tt>true</tt> if the iteration has more elements. (In other
   * words, returns <tt>true</tt> if <tt>next</tt> would return an element
   * rather than throwing an exception.)
opends/src/server/org/opends/server/backends/jeb/SortValues.java
New file
@@ -0,0 +1,216 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import java.util.List;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Entry;
import org.opends.server.types.SortKey;
import org.opends.server.types.SortOrder;
/**
 * This class defines a data structure that holds a set of attribute values that
 * are associated with a sort order for a given entry.  Any or all of the
 * attribute values may be {@code null} if the entry does not include any values
 * for the attribute type targeted by the corresponding sort key.
 * <BR><BR>
 * This class implements the {@code Comparable} interface and may therefore be
 * used to order the elements in components like {@code TreeMap} and
 * {@code TreeSet}.
 */
public class SortValues
       implements Comparable<SortValues>
{
  // The set of sort keys in this sort order.
  private AttributeValue[] values;
  // The entry ID for the entry associated with this sort values.
  private EntryID entryID;
  // The sort order for this set of sort values.
  private SortOrder sortOrder;
  /**
   * Creates a new sort values object with the provided information.
   *
   * @param  entryID    The entry ID for the entry associated with this set of
   *                    values.
   * @param  entry      The entry containing the values to extract and use when
   *                    sorting.
   * @param  sortOrder  The sort order to use to obtain the necessary values.
   */
  public SortValues(EntryID entryID, Entry entry, SortOrder sortOrder)
  {
    this.entryID   = entryID;
    this.sortOrder = sortOrder;
    SortKey[] sortKeys = sortOrder.getSortKeys();
    values = new AttributeValue[sortKeys.length];
    for (int i=0; i < sortKeys.length; i++)
    {
      SortKey sortKey = sortKeys[i];
      AttributeType attrType = sortKey.getAttributeType();
      List<Attribute> attrList = entry.getAttribute(attrType);
      if (attrList != null)
      {
        AttributeValue sortValue = null;
        // There may be multiple versions of this attribute in the target entry
        // (e.g., with different sets of options), and it may also be a
        // multivalued attribute.  In that case, we need to find the value that
        // is the best match for the corresponding sort key (i.e., for sorting
        // in ascending order, we want to find the lowest value; for sorting in
        // descending order, we want to find the highest value).  This is
        // handled by the SortKey.compareValues method.
        for (Attribute a : attrList)
        {
          for (AttributeValue v : a.getValues())
          {
            if (sortValue == null)
            {
              sortValue = v;
            }
            else if (sortKey.compareValues(v, sortValue) < 0)
            {
              sortValue = v;
            }
          }
        }
        values[i] = sortValue;
      }
    }
  }
  /**
   * Compares this set of sort values with the provided set of values to
   * determine their relative order in a sorted list.
   *
   * @param  sortValues  The set of values to compare against this sort values.
   *                     It must also have the same sort order as this set of
   *                     values.
   *
   * @return  A negative value if this sort values object should come before the
   *          provided values in a sorted list, a positive value if this sort
   *          values object should come after the provided values in a sorted
   *          list, or zero if there is no significant difference in their
   *          relative order.
   */
  public int compareTo(SortValues sortValues)
  {
    SortKey[] sortKeys = sortOrder.getSortKeys();
    for (int i=0; i < values.length; i++)
    {
      int compareValue = sortKeys[i].compareValues(values[i],
                                          sortValues.values[i]);
      if (compareValue != 0)
      {
        return compareValue;
      }
    }
    // If we've gotten here, then we can't tell a difference between the sets of
    // sort values, so sort based on entry ID.
    long idDifference = (entryID.longValue() - sortValues.entryID.longValue());
    if (idDifference < 0)
    {
      return -1;
    }
    else if (idDifference > 0)
    {
      return 1;
    }
    else
    {
      return 0;
    }
  }
  /**
   * Retrieves a string representation of this sort values object.
   *
   * @return  A string representation of this sort values object.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this sort values object to the provided
   * buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("SortValues(");
    SortKey[] sortKeys = sortOrder.getSortKeys();
    for (int i=0; i < sortKeys.length; i++)
    {
      if (i > 0)
      {
        buffer.append(", ");
      }
      if (sortKeys[i].ascending())
      {
        buffer.append("+");
      }
      else
      {
        buffer.append("-");
      }
      buffer.append(sortKeys[i].getAttributeType().getNameOrOID());
      buffer.append("=");
      buffer.append(values[i].getStringValue());
    }
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/controls/ServerSideSortRequestControl.java
New file
@@ -0,0 +1,456 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.asn1.ASN1Boolean;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Control;
import org.opends.server.types.LDAPException;
import org.opends.server.types.SortKey;
import org.opends.server.types.SortOrder;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements the server-side sort request control as defined in RFC
 * 2891 section 1.1.  The ASN.1 description for the control value is:
 * <BR><BR>
 * <PRE>
 * SortKeyList ::= SEQUENCE OF SEQUENCE {
 *            attributeType   AttributeDescription,
 *            orderingRule    [0] MatchingRuleId OPTIONAL,
 *            reverseOrder    [1] BOOLEAN DEFAULT FALSE }
 * </PRE>
 */
public class ServerSideSortRequestControl
       extends Control
{
  /**
   * The BER type to use when encoding the orderingRule element.
   */
  private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80;
  /**
   * The BER type to use when encoding the reverseOrder element.
   */
  private static final byte TYPE_REVERSE_ORDER = (byte) 0x81;
  // The sort order associated with this control.
  private SortOrder sortOrder;
  /**
   * Creates a new server-side sort request control based on the provided sort
   * order.
   *
   * @param  sortOrder  The sort order to use for this control.
   */
  public ServerSideSortRequestControl(SortOrder sortOrder)
  {
    super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, false,
          encodeControlValue(sortOrder));
    this.sortOrder = sortOrder;
  }
  /**
   * Creates a new server-side sort request control based on the definition in
   * the provided sort order string.  This is only intended for client-side use,
   * and controls created with this constructor should not attempt to use the
   * generated sort order for any purpose.
   *
   * @param  sortOrderString  The string representation of the sort order to use
   *                          for the control.
   *
   * @throws  LDAPException  If the provided sort order string could not be
   *                         decoded.
   */
  public ServerSideSortRequestControl(String sortOrderString)
         throws LDAPException
  {
    super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, false,
          encodeControlValue(sortOrderString));
    this.sortOrder = null;
  }
  /**
   * Creates a new server-side sort request control with the provided
   * information.
   *
   * @param  oid           The OID to use for this control.
   * @param  isCritical    Indicates whether support for this control should be
   *                       considered a critical part of the server processing.
   * @param  controlValue  The encoded value for this control.
   * @param  sortOrder     sort order associated with this server-side sort
   *                       control.
   */
  private ServerSideSortRequestControl(String oid, boolean isCritical,
                                       ASN1OctetString controlValue,
                                       SortOrder sortOrder)
  {
    super(oid, isCritical, controlValue);
    this.sortOrder = sortOrder;
  }
  /**
   * Retrieves the sort order for this server-side sort request control.
   *
   * @return  The sort order for this server-side sort request control.
   */
  public SortOrder getSortOrder()
  {
    return sortOrder;
  }
  /**
   * Encodes the provided sort order object in a manner suitable for use as the
   * value of this control.
   *
   * @param  sortOrder  The sort order to be encoded.
   *
   * @return  The ASN.1 octet string containing the encoded sort order.
   */
  private static ASN1OctetString encodeControlValue(SortOrder sortOrder)
  {
    SortKey[] sortKeys = sortOrder.getSortKeys();
    ArrayList<ASN1Element> keyList =
         new ArrayList<ASN1Element>(sortKeys.length);
    for (SortKey sortKey : sortKeys)
    {
      ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3);
      elementList.add(new ASN1OctetString(
                               sortKey.getAttributeType().getNameOrOID()));
      if (sortKey.getOrderingRule() != null)
      {
        elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID,
                                 sortKey.getOrderingRule().getNameOrOID()));
      }
      if (! sortKey.ascending())
      {
        elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, false));
      }
      keyList.add(new ASN1Sequence(elementList));
    }
    return new ASN1OctetString(new ASN1Sequence(keyList).encode());
  }
  /**
   * Encodes the provided sort order string in a manner suitable for use as the
   * value of this control.
   *
   * @param  sortOrderString  The sort order string to be encoded.
   *
   * @return  The ASN.1 octet string containing the encoded sort order.
   *
   * @throws  LDAPException  If the provided sort order string cannot be decoded
   *                         to create the control value.
   */
  private static ASN1OctetString encodeControlValue(String sortOrderString)
          throws LDAPException
  {
    StringTokenizer tokenizer = new StringTokenizer(sortOrderString, ",");
    ArrayList<ASN1Element> keyList = new ArrayList<ASN1Element>();
    while (tokenizer.hasMoreTokens())
    {
      String token = tokenizer.nextToken().trim();
      boolean reverseOrder = false;
      if (token.startsWith("-"))
      {
        reverseOrder = true;
        token = token.substring(1);
      }
      else if (token.startsWith("+"))
      {
        token = token.substring(1);
      }
      int colonPos = token.indexOf(':');
      if (colonPos < 0)
      {
        if (token.length() == 0)
        {
          int    msgID   = MSGID_SORTREQ_CONTROL_NO_ATTR_NAME;
          String message = getMessage(msgID, sortOrderString);
          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                  message);
        }
        if (reverseOrder)
        {
          ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(2);
          elementList.add(new ASN1OctetString(token));
          elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder));
          keyList.add(new ASN1Sequence(elementList));
        }
        else
        {
          ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(1);
          elementList.add(new ASN1OctetString(token));
          keyList.add(new ASN1Sequence(elementList));
        }
      }
      else if (colonPos == 0)
      {
        int    msgID   = MSGID_SORTREQ_CONTROL_NO_ATTR_NAME;
        String message = getMessage(msgID, sortOrderString);
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                message);
      }
      else if (colonPos == (token.length() - 1))
      {
        int    msgID   = MSGID_SORTREQ_CONTROL_NO_MATCHING_RULE;
        String message = getMessage(msgID, sortOrderString);
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                message);
      }
      else
      {
        String attrName = token.substring(0, colonPos);
        String ruleID   = token.substring(colonPos+1);
        if (reverseOrder)
        {
          ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3);
          elementList.add(new ASN1OctetString(attrName));
          elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID, ruleID));
          elementList.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder));
          keyList.add(new ASN1Sequence(elementList));
        }
        else
        {
          ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(2);
          elementList.add(new ASN1OctetString(attrName));
          elementList.add(new ASN1OctetString(TYPE_ORDERING_RULE_ID, ruleID));
          keyList.add(new ASN1Sequence(elementList));
        }
      }
    }
    if (keyList.isEmpty())
    {
      int    msgID   = MSGID_SORTREQ_CONTROL_NO_SORT_KEYS;
      String message = getMessage(msgID);
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    return new ASN1OctetString(new ASN1Sequence(keyList).encode());
  }
  /**
   * Creates a new server-side sort request control from the contents of the
   * provided control.
   *
   * @param  control  The generic control containing the information to use to
   *                  create this server-side sort request control.  It must not
   *                  be {@code null}.
   *
   * @return  The server-side sort request control decoded from the provided
   *          control.
   *
   * @throws  LDAPException  If this control cannot be decoded as a valid
   *                         server-side sort request control.
   */
  public static ServerSideSortRequestControl decodeControl(Control control)
         throws LDAPException
  {
    ASN1OctetString controlValue = control.getValue();
    if (controlValue == null)
    {
      int    msgID   = MSGID_SORTREQ_CONTROL_NO_VALUE;
      String message = getMessage(msgID);
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    try
    {
      ASN1Sequence orderSequence =
           ASN1Sequence.decodeAsSequence(controlValue.value());
      ArrayList<ASN1Element> orderElements = orderSequence.elements();
      SortKey[] sortKeys = new SortKey[orderElements.size()];
      if (sortKeys.length == 0)
      {
        int    msgID   = MSGID_SORTREQ_CONTROL_NO_SORT_KEYS;
        String message = getMessage(msgID);
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
      }
      for (int i=0; i < sortKeys.length; i++)
      {
        ASN1Sequence keySequence = orderElements.get(i).decodeAsSequence();
        ArrayList<ASN1Element> keyElements = keySequence.elements();
        String attrName =
             keyElements.get(0).decodeAsOctetString().stringValue().
                  toLowerCase();
        AttributeType attrType = DirectoryServer.getAttributeType(attrName,
                                                                  false);
        if (attrType == null)
        {
          int    msgID   = MSGID_SORTREQ_CONTROL_UNDEFINED_ATTR;
          String message = getMessage(msgID, attrName);
          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                  message);
        }
        OrderingMatchingRule orderingRule = null;
        boolean ascending = true;
        for (int j=1; j < keyElements.size(); j++)
        {
          ASN1Element e = keyElements.get(j);
          switch (e.getType())
          {
            case TYPE_ORDERING_RULE_ID:
              String orderingRuleID =
                   e.decodeAsOctetString().stringValue().toLowerCase();
              orderingRule =
                   DirectoryServer.getOrderingMatchingRule(orderingRuleID);
              if (orderingRule == null)
              {
                int    msgID   = MSGID_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE;
                String message = getMessage(msgID, orderingRuleID);
                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                        message);
              }
              break;
            case TYPE_REVERSE_ORDER:
              ascending = ! e.decodeAsBoolean().booleanValue();
              break;
            default:
              int    msgID   = MSGID_SORTREQ_CONTROL_INVALID_SEQ_ELEMENT_TYPE;
              String message = getMessage(msgID, byteToHex(e.getType()));
              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID,
                                      message);
          }
        }
        if ((orderingRule == null) &&
            (attrType.getOrderingMatchingRule() == null))
        {
          int    msgID   = MSGID_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR;
          String message = getMessage(msgID, attrName);
          throw new LDAPException(LDAPResultCode.CONSTRAINT_VIOLATION, msgID,
                                  message);
        }
        sortKeys[i] = new SortKey(attrType, ascending, orderingRule);
      }
      return new ServerSideSortRequestControl(control.getOID(),
                                              control.isCritical(),
                                              controlValue,
                                              new SortOrder(sortKeys));
    }
    catch (LDAPException le)
    {
      throw le;
    }
    catch (Exception e)
    {
      int    msgID   = MSGID_SORTREQ_CONTROL_CANNOT_DECODE_VALUE;
      String message = getMessage(msgID, getExceptionMessage(e));
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message, e);
    }
  }
  /**
   * Retrieves a string representation of this server-side sort request control.
   *
   * @return  A string representation of this server-side sort request control.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this server-side sort request control
   * to the provided buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("ServerSideSortRequestControl(");
    if (sortOrder != null)
    {
      buffer.append(sortOrder);
    }
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/controls/ServerSideSortResponseControl.java
New file
@@ -0,0 +1,277 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Enumerated;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.types.Control;
import org.opends.server.types.LDAPException;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements the server-side sort response control as defined in RFC
 * 2891 section 1.2.  The ASN.1 description for the control value is:
 * <BR><BR>
 * <PRE>
 * SortResult ::= SEQUENCE {
 *    sortResult  ENUMERATED {
 *        success                   (0), -- results are sorted
 *        operationsError           (1), -- server internal failure
 *        timeLimitExceeded         (3), -- timelimit reached before
 *                                       -- sorting was completed
 *        strongAuthRequired        (8), -- refused to return sorted
 *                                       -- results via insecure
 *                                       -- protocol
 *        adminLimitExceeded       (11), -- too many matching entries
 *                                       -- for the server to sort
 *        noSuchAttribute          (16), -- unrecognized attribute
 *                                       -- type in sort key
 *        inappropriateMatching    (18), -- unrecognized or
 *                                       -- inappropriate matching
 *                                       -- rule in sort key
 *        insufficientAccessRights (50), -- refused to return sorted
 *                                       -- results to this client
 *        busy                     (51), -- too busy to process
 *        unwillingToPerform       (53), -- unable to sort
 *        other                    (80)
 *        },
 *  attributeType [0] AttributeDescription OPTIONAL }
 * </PRE>
 */
public class ServerSideSortResponseControl
       extends Control
{
  /**
   * The BER type to use when encoding the attribute type element.
   */
  private static final byte TYPE_ATTRIBUTE_TYPE = (byte) 0x80;
  // The result code for the sort result.
  private int resultCode;
  // The attribute type for the sort result.
  private String attributeType;
  /**
   * Creates a new server-side sort response control based on the provided
   * result code and attribute type.
   *
   * @param  resultCode     The result code for the sort result.
   * @param  attributeType  The attribute type for the sort result (or
   *                        {@code null} if there is none).
   */
  public ServerSideSortResponseControl(int resultCode, String attributeType)
  {
    super(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL, false,
          encodeControlValue(resultCode, attributeType));
    this.resultCode    = resultCode;
    this.attributeType = attributeType;
  }
  /**
   * Creates a new server-side sort response control with the provided
   * information.
   *
   * @param  oid            The OID to use for this control.
   * @param  isCritical     Indicates whether support for this control should be
   *                        considered a critical part of the server processing.
   * @param  controlValue   The encoded value for this control.
   * @param  resultCode     The result code for the sort result.
   * @param  attributeType  The attribute type for the sort result.
   */
  private ServerSideSortResponseControl(String oid, boolean isCritical,
                                        ASN1OctetString controlValue,
                                        int resultCode,
                                        String attributeType)
  {
    super(oid, isCritical, controlValue);
    this.resultCode    = resultCode;
    this.attributeType = attributeType;
  }
  /**
   * Retrieves the result code for this sort result.
   *
   * @return  The result code for this sort result.
   */
  public int getResultCode()
  {
    return resultCode;
  }
  /**
   * Retrieves the attribute type for this sort result.
   *
   * @return  The attribute type for this sort result, or {@code null} if there
   *          is none.
   */
  public String getAttributeType()
  {
    return attributeType;
  }
  /**
   * Encodes the provided set of result codes and attribute types in a manner
   * suitable for use as the value of this control.
   *
   * @param  resultCode     The result code for the sort result.
   * @param  attributeType  The attribute type for the sort result, or
   *                        {@code null} if there is none.
   *
   * @return  The ASN.1 octet string containing the encoded sort result.
   */
  private static ASN1OctetString encodeControlValue(int resultCode,
                                                    String attributeType)
  {
    ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
    elements.add(new ASN1Enumerated(resultCode));
    if (attributeType != null)
    {
      elements.add(new ASN1OctetString(TYPE_ATTRIBUTE_TYPE, attributeType));
    }
    return new ASN1OctetString(new ASN1Sequence(elements).encode());
  }
  /**
   * Creates a new server-side sort response control from the contents of the
   * provided control.
   *
   * @param  control  The generic control containing the information to use to
   *                  create this server-side sort response control.  It must
   *                  not be {@code null}.
   *
   * @return  The server-side sort response control decoded from the provided
   *          control.
   *
   * @throws  LDAPException  If this control cannot be decoded as a valid
   *                         server-side sort response control.
   */
  public static ServerSideSortResponseControl decodeControl(Control control)
         throws LDAPException
  {
    ASN1OctetString controlValue = control.getValue();
    if (controlValue == null)
    {
      int    msgID   = MSGID_SORTRES_CONTROL_NO_VALUE;
      String message = getMessage(msgID);
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    try
    {
      ArrayList<ASN1Element> elements =
           ASN1Sequence.decodeAsSequence(control.getValue().value()).elements();
      int resultCode = elements.get(0).decodeAsEnumerated().intValue();
      String attributeType = null;
      if (elements.size() > 1)
      {
        attributeType = elements.get(1).decodeAsOctetString().stringValue();
      }
      return new ServerSideSortResponseControl(control.getOID(),
                                               control.isCritical(),
                                               control.getValue(), resultCode,
                                               attributeType);
    }
    catch (Exception e)
    {
      int    msgID   = MSGID_SORTRES_CONTROL_CANNOT_DECODE_VALUE;
      String message = getMessage(msgID, getExceptionMessage(e));
      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message, e);
    }
  }
  /**
   * Retrieves a string representation of this server-side sort response
   * control.
   *
   * @return  A string representation of this server-side sort response control.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this server-side sort response control
   * to the provided buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("ServerSideSortResponseControl(resultCode=");
    buffer.append(resultCode);
    if (attributeType != null)
    {
      buffer.append(", attributeType=");
      buffer.append(attributeType);
    }
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6041,6 +6041,57 @@
  /**
   * The message ID for the message that will be used if a sort key string
   * has an invalid order indicator.  This takes a single argument, which is the
   * sort key string.
   */
  public static final int MSGID_SORTKEY_INVALID_ORDER_INDICATOR =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 603;
  /**
   * The message ID for the message that will be used if a sort key string
   * has an undefined attribute type.  This takes two arguments, which are the
   * sort key string and the name of the attribute type.
   */
  public static final int MSGID_SORTKEY_UNDEFINED_TYPE =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 604;
  /**
   * The message ID for the message that will be used if a sort key string
   * has an attribute type with no ordering matching rule.  This takes two
   * arguments, which are the sort key string and the name of the attribute
   * type.
   */
  public static final int MSGID_SORTKEY_NO_ORDERING_RULE =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 605;
  /**
   * The message ID for the message that will be used if a sort key string
   * has an undefined ordering rule.  This takes two arguments, which are the
   * sort key string and the name of the ordering rule.
   */
  public static final int MSGID_SORTKEY_UNDEFINED_ORDERING_RULE =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 606;
  /**
   * The message ID for the message that will be used if a sort order string
   * does not include any sort keys.  This takes a single argument, which is the
   * sort order string.
   */
  public static final int MSGID_SORTORDER_DECODE_NO_KEYS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 607;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -8219,6 +8270,28 @@
    registerMessage(MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES,
                    "You do not have sufficient privileges to use the " +
                    "proxied authorization control");
    registerMessage(MSGID_SORTKEY_INVALID_ORDER_INDICATOR,
                    "The provided sort key value %s is invalid because it " +
                    "does not start with either '+' (to indicate sorting in " +
                    "ascending order) or '-' (to indicate sorting in " +
                    "descending order)");
    registerMessage(MSGID_SORTKEY_UNDEFINED_TYPE,
                    "The provided sort key value %s is invalid because it " +
                    "references undefined attribute type %s");
    registerMessage(MSGID_SORTKEY_NO_ORDERING_RULE,
                    "The provided sort key value %s is invalid because " +
                    "attribute type %s does not have a default ordering " +
                    "matching rule and no specific rule was provided");
    registerMessage(MSGID_SORTKEY_UNDEFINED_ORDERING_RULE,
                    "The provided sort key value %s is invalid because " +
                    "it references undefined ordering matching rule %s");
    registerMessage(MSGID_SORTORDER_DECODE_NO_KEYS,
                    "The provided sort order string \"%s\" is invalid " +
                    "because it does not contain any sort keys");
  }
}
opends/src/server/org/opends/server/messages/JebMessages.java
@@ -1095,6 +1095,23 @@
       CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 138;
  /**
   * The message ID to use if an error occurs while attempting to retrieve an
   * entry to examine while sorting.  This takes two arguments, which are a
   * string representation of the entry ID and a message explaining the
   * problem that occurred.
   */
  public static final int MSGID_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY =
       CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 139;
  /**
   * The message ID of an error indicating that the base entry of a search
   * operation does not exist.  This message takes one string argument which is
   * the DN of the search base entry.
   */
  public static final int MSGID_JEB_SEARCH_CANNOT_SORT_UNINDEXED =
       CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 140;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -1193,6 +1210,9 @@
                    "There is no index configured for attribute type '%s'");
    registerMessage(MSGID_JEB_SEARCH_NO_SUCH_OBJECT,
                    "The search base entry '%s' does not exist");
    registerMessage(MSGID_JEB_SEARCH_CANNOT_SORT_UNINDEXED,
                    "The search results cannot be sorted because the given " +
                    "search request is not indexed");
    registerMessage(MSGID_JEB_ADD_NO_SUCH_OBJECT,
                    "The entry '%s' cannot be added because its parent " +
                    "entry does not exist");
@@ -1394,5 +1414,10 @@
    registerMessage(MSGID_JEB_REBUILD_BACKEND_ONLINE,
                    "Rebuilding system index(es) must be done with the " +
                    "backend containing the base DN disabled");
    registerMessage(MSGID_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY,
                    "Unable to examine the entry with ID %s for sorting " +
                    "purposes:  %s");
  }
}
opends/src/server/org/opends/server/messages/ProtocolMessages.java
@@ -4379,6 +4379,8 @@
  public static final int MSGID_LDAP_CONNHANDLER_NO_TRUSTMANAGER_DN =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_SEVERE_ERROR | 403;
  /**
   * The message ID for the message that will be used as the description of the
   * configuration attribute specifying whether to enable the LDAPS
@@ -4400,6 +4402,114 @@
  /**
   * The message ID for the message that will be used if the server-side sort
   * request control does not have a value.  It does not take any arguments.
   */
  public static final int MSGID_SORTREQ_CONTROL_NO_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 406;
  /**
   * The message ID for the message that will be used if the server-side sort
   * request control references an undefined attribute type.  This takes a
   * single argument, which is the name of the attribute type.
   */
  public static final int MSGID_SORTREQ_CONTROL_UNDEFINED_ATTR =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 407;
  /**
   * The message ID for the message that will be used if the server-side sort
   * request control references an undefined matching rule.  This takes a
   * single argument, which is the name of the matching rule.
   */
  public static final int MSGID_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 408;
  /**
   * The message ID for the message that will be used if the server-side sort
   * request control value sequence has an element with an invalid type.  This
   * takes a single argument, which is the hex representation of the unsupported
   * type value.
   */
  public static final int MSGID_SORTREQ_CONTROL_INVALID_SEQ_ELEMENT_TYPE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 409;
  /**
   * The message ID for the message that will be used if an error occurs while
   * decoding the server-side sort request control value.  This takes a single
   * argument, which is a message explaining the problem that occurred.
   */
  public static final int MSGID_SORTREQ_CONTROL_CANNOT_DECODE_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 410;
  /**
   * The message ID for the message that will be used if the server-side sort
   * response control does not have a value.  It does not take any arguments.
   */
  public static final int MSGID_SORTRES_CONTROL_NO_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 411;
  /**
   * The message ID for the message that will be used if an error occurs while
   * decoding the server-side sort response control value.  This takes a single
   * argument, which is a message explaining the problem that occurred.
   */
  public static final int MSGID_SORTRES_CONTROL_CANNOT_DECODE_VALUE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 412;
  /**
   * The message ID for the message that will be used if a sort order string
   * has a zero-length attribute name.  This takes a single argument, which is
   * the sort order string.
   */
  public static final int MSGID_SORTREQ_CONTROL_NO_ATTR_NAME =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 413;
  /**
   * The message ID for the message that will be used if a sort order string
   * has a zero-length matching rule name.  This takes a single argument, which
   * is the sort order string.
   */
  public static final int MSGID_SORTREQ_CONTROL_NO_MATCHING_RULE =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 414;
  /**
   * The message ID for the message that will be used if the server-side sort
   * control does not have any sort keys.  This does not take any arguments.
   */
  public static final int MSGID_SORTREQ_CONTROL_NO_SORT_KEYS =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 415;
  /**
   * The message ID for the message that will be used if the server-side sort
   * control includes an attribute for which no ordering rule is available.
   * This does not take any arguments.
   */
  public static final int MSGID_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 416;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6285,6 +6395,55 @@
    registerMessage(MSGID_ADDRESSMASK_FORMAT_DECODE_ERROR,
            "Cannot decode the provided address mask because the it has an" +
            "invalid format");
    registerMessage(MSGID_SORTREQ_CONTROL_NO_VALUE,
                    "Unable to decode the provided control as a server-side " +
                    "sort request control because it does not include a " +
                    "control value");
    registerMessage(MSGID_SORTREQ_CONTROL_UNDEFINED_ATTR,
                    "Unable to process the provided server-side sort " +
                    "request control because it references attribute " +
                    "type %s which is not defined in the server schema");
    registerMessage(MSGID_SORTREQ_CONTROL_UNDEFINED_ORDERING_RULE,
                    "Unable to process the provided server-side sort request " +
                    "control because it references undefined ordering " +
                    "matching rule %s");
    registerMessage(MSGID_SORTREQ_CONTROL_INVALID_SEQ_ELEMENT_TYPE,
                    "Unable to process the provided server-side sort request " +
                    "control because the value sequence contains an element " +
                    "with an unsupported type of %s");
    registerMessage(MSGID_SORTREQ_CONTROL_CANNOT_DECODE_VALUE,
                    "Unable to process the provided server-side sort request " +
                    "control because an error occurred while attempting to " +
                    "decode the control value:  %s");
    registerMessage(MSGID_SORTREQ_CONTROL_NO_ATTR_NAME,
                    "Unable to process the provided server-side sort " +
                    "request control because the sort order string \"%s\" " +
                    "included a sort key with no attribute name");
    registerMessage(MSGID_SORTREQ_CONTROL_NO_MATCHING_RULE,
                    "Unable to process the provided server-side sort " +
                    "request control because the sort order string \"%s\" " +
                    "included a sort key with a colon but no matching rule " +
                    "name");
    registerMessage(MSGID_SORTREQ_CONTROL_NO_SORT_KEYS,
                    "Unable to process the provided server-side sort " +
                    "request control because it did not contain any sort keys");
    registerMessage(MSGID_SORTREQ_CONTROL_NO_ORDERING_RULE_FOR_ATTR,
                    "Unable to process the provided server-side sort " +
                    "request control because it included attribute %s which " +
                    "does not have a default ordering matching rule and no " +
                    "ordering rule was specified in the sort key");
    registerMessage(MSGID_SORTRES_CONTROL_NO_VALUE,
                    "Unable to decode the provided control as a server-side " +
                    "sort response control because it does not include a " +
                    "control value");
    registerMessage(MSGID_SORTRES_CONTROL_CANNOT_DECODE_VALUE,
                    "Unable to process the provided server-side sort " +
                    "response control because an error occurred while " +
                    "attempting to decode the control value:  %s");
  }
}
opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -6949,6 +6949,25 @@
  /**
   * The message ID for the message that will be used as the description of the
   * sortOrder option for the ldapsearch tool.  It does not take any arguments.
   */
  public static final int MSGID_DESCRIPTION_SORT_ORDER =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 876;
  /**
   * The message ID for the message that will be used if the provided sort order
   * is invalid.  This takes a single argument, which is a message explaining
   * the problem that occurred.
   */
  public static final int MSGID_LDAP_SORTCONTROL_INVALID_ORDER =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 877;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -7368,6 +7387,8 @@
    registerMessage(MSGID_DESCRIPTION_MATCHED_VALUES_FILTER,
                    "Use the LDAP matched values control with the provided " +
                    "filter");
    registerMessage(MSGID_DESCRIPTION_SORT_ORDER,
                    "Sort the results using the provided sort order");
    registerMessage(MSGID_COMPARE_CANNOT_BASE64_DECODE_ASSERTION_VALUE,
                    "The assertion value was indicated to be base64-encoded, " +
                    "but an error occurred while trying to decode the value");
@@ -7659,6 +7680,8 @@
                    "control was invalid:  %s");
    registerMessage(MSGID_LDAP_MATCHEDVALUES_INVALID_FILTER,
                    "The provided matched values filter was invalid:  %s");
    registerMessage(MSGID_LDAP_SORTCONTROL_INVALID_ORDER,
                    "The provided sort order was invalid:  %s");
    registerMessage(MSGID_LDAPMODIFY_PREREAD_NO_VALUE,
                    "The pre-read response control did not include a value");
    registerMessage(MSGID_LDAPMODIFY_PREREAD_CANNOT_DECODE_VALUE,
opends/src/server/org/opends/server/tools/LDAPSearch.java
@@ -45,6 +45,7 @@
import org.opends.server.controls.PagedResultsControl;
import org.opends.server.controls.PersistentSearchChangeType;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ServerSideSortRequestControl;
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.Base64;
import org.opends.server.util.PasswordReader;
@@ -602,6 +603,7 @@
    StringArgument    pSearchInfo              = null;
    StringArgument    saslOptions              = null;
    StringArgument    searchScope              = null;
    StringArgument    sortOrder                = null;
    StringArgument    trustStorePath           = null;
    StringArgument    trustStorePassword       = null;
@@ -788,6 +790,11 @@
                                     MSGID_DESCRIPTION_MATCHED_VALUES_FILTER);
      argParser.addArgument(matchedValuesFilter);
      sortOrder = new StringArgument("sortorder", 'S', "sortOrder", false,
                                     false, true, "{sortOrder}", null, null,
                                     MSGID_DESCRIPTION_SORT_ORDER);
      argParser.addArgument(sortOrder);
      controlStr =
           new StringArgument("control", 'J', "control", false, true, true,
                    "{controloid[:criticality[:value|::b64value|:<fileurl]]}",
@@ -1260,6 +1267,23 @@
      searchOptions.getControls().add(new LDAPControl(mvc));
    }
    if (sortOrder.isPresent())
    {
      try
      {
        searchOptions.getControls().add(
             new LDAPControl(new ServerSideSortRequestControl(
                                      sortOrder.getValue())));
      }
      catch (LDAPException le)
      {
        int    msgID   = MSGID_LDAP_SORTCONTROL_INVALID_ORDER;
        String message = getMessage(msgID, le.getErrorMessage());
        err.println(wrapText(message, MAX_LINE_WIDTH));
        return 1;
      }
    }
    // Set the connection options.
    connectionOptions.setSASLExternal(saslExternal.isPresent());
    if(saslOptions.isPresent())
@@ -1535,6 +1559,5 @@
      }
    }
  }
}
opends/src/server/org/opends/server/types/SortKey.java
New file
@@ -0,0 +1,278 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import org.opends.server.api.OrderingMatchingRule;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class defines a data structure that may be used as a sort key.
 * It includes an attribute type and a boolean value that indicates
 * whether the sort should be ascending or descending.  It may also
 * contain a specific ordering matching rule that should be used for
 * the sorting process, although if none is provided it will use the
 * default ordering matching rule for the attribute type.
 */
public class SortKey
{
  // The attribute type for this sort key.
  private AttributeType attributeType;
  // The indication of whether the sort should be ascending.
  private boolean ascending;
  // The ordering matching rule to use with this sort key.
  private OrderingMatchingRule orderingRule;
  /**
   * Creates a new sort key with the provided information.
   *
   * @param  attributeType  The attribute type for this sort key.
   * @param  ascending      Indicates whether the sort should be in
   *                        ascending order rather than descending.
   */
  public SortKey(AttributeType attributeType, boolean ascending)
  {
    this.attributeType = attributeType;
    this.ascending     = ascending;
    orderingRule = null;
  }
  /**
   * Creates a new sort key with the provided information.
   *
   * @param  attributeType  The attribute type for this sort key.
   * @param  ascending      Indicates whether the sort should be in
   *                        ascending order rather than descending.
   * @param  orderingRule   The ordering matching rule to use with
   *                        this sort key.
   */
  public SortKey(AttributeType attributeType, boolean ascending,
                 OrderingMatchingRule orderingRule)
  {
    this.attributeType = attributeType;
    this.ascending     = ascending;
    this.orderingRule  = orderingRule;
  }
  /**
   * Retrieves the attribute type for this sort key.
   *
   * @return  The attribute type for this sort key.
   */
  public AttributeType getAttributeType()
  {
    return attributeType;
  }
  /**
   * Indicates whether the specified attribute should be sorted in
   * ascending order.
   *
   * @return  {@code true} if the attribute should be sorted in
   *          ascending order, or {@code false} if it should be sorted
   *          in descending order.
   */
  public boolean ascending()
  {
    return ascending;
  }
  /**
   * Retrieves the ordering matching rule to use with this sort key.
   *
   * @return  The ordering matching rule to use with this sort key.
   */
  public OrderingMatchingRule getOrderingRule()
  {
    return orderingRule;
  }
  /**
   * Compares the provided values using this sort key.
   *
   * @param  value1  The first value to be compared.
   * @param  value2  The second value to be compared.
   *
   * @return  A negative value if the first value should come before
   *          the second in a sorted list, a positive value if the
   *          first value should come after the second in a sorted
   *          list, or zero if there is no relative difference between
   *          the values.
   */
  public int compareValues(AttributeValue value1,
                           AttributeValue value2)
  {
    // A null value will always come after a non-null value.
    if (value1 == null)
    {
      if (value2 == null)
      {
        return 0;
      }
      else
      {
        return 1;
      }
    }
    else if (value2 == null)
    {
      return -1;
    }
    // Use the ordering matching rule if one is provided.  Otherwise,
    // fall back on the default ordering rule for the attribute type.
    if (orderingRule == null)
    {
      try
      {
        OrderingMatchingRule rule =
             attributeType.getOrderingMatchingRule();
        if (rule == null)
        {
          return 0;
        }
        if (ascending)
        {
          return rule.compareValues(value1.getNormalizedValue(),
                                    value2.getNormalizedValue());
        }
        else
        {
          return rule.compareValues(value2.getNormalizedValue(),
                                    value1.getNormalizedValue());
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        return 0;
      }
    }
    else
    {
      try
      {
        if (ascending)
        {
          return orderingRule.compareValues(
                      orderingRule.normalizeValue(value1.getValue()),
                      orderingRule.normalizeValue(value2.getValue()));
        }
        else
        {
          return orderingRule.compareValues(
                      orderingRule.normalizeValue(value2.getValue()),
                      orderingRule.normalizeValue(value1.getValue()));
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        return 0;
      }
    }
  }
  /**
   * Retrieves a string representation of this sort key.
   *
   * @return  A string representation of this sort key.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this sort key to the
   * provided buffer.
   *
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("SortKey(");
    if (ascending)
    {
      buffer.append("+");
    }
    else
    {
      buffer.append("-");
    }
    buffer.append(attributeType.getNameOrOID());
    if (orderingRule != null)
    {
      buffer.append(":");
      buffer.append(orderingRule.getNameOrOID());
    }
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/types/SortOrder.java
New file
@@ -0,0 +1,135 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a data structure that defines a set of sort
 * criteria that may be used to order entries in a set of search
 * results.  The sort order object is comprised of one or more sort
 * keys, which indicate which attribute types should be used to
 * perform the sort and information about the ordering to use for
 * those attributes.  If the sort order has multiple sort keys, then
 * the first sort key will be used as the primary sort criteria, and
 * the second will only be used in cases where the values of the
 * attribute associated with the first sort key are equal, the third
 * will only be used if the first and second values are equal, etc.
 * If all of the sort key attributes for two entries are identical,
 * then the relative order for those entries is undefined.
 */
public class SortOrder
{
  // The set of sort keys in this sort order.
  private SortKey[] sortKeys;
  /**
   * Creates a new sort order with a single key.
   *
   * @param  sortKey  The sort key to use in this sort order.
   */
  public SortOrder(SortKey sortKey)
  {
    this.sortKeys = new SortKey[] { sortKey };
  }
  /**
   * Creates a new sort order with the provided set of sort keys.
   *
   * @param  sortKeys  The set of sort keys to use for this sort
   *                   order.
   */
  public SortOrder(SortKey[] sortKeys)
  {
    this.sortKeys = new SortKey[sortKeys.length];
    System.arraycopy(sortKeys, 0, this.sortKeys, 0, sortKeys.length);
  }
  /**
   * Retrieves the sort keys for this sort order.
   *
   * @return  The sort keys for this sort order.
   */
  public SortKey[] getSortKeys()
  {
    return sortKeys;
  }
  /**
   * Retrieves a string representation of this sort order.
   *
   * @return  A string representation of this sort order.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this sort order to the
   * provided buffer.
   *
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("SortOrder(");
    if (sortKeys.length > 0)
    {
      sortKeys[0].toString(buffer);
      for (int i=1; i < sortKeys.length; i++)
      {
        buffer.append(",");
        sortKeys[i].toString(buffer);
      }
    }
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1819,6 +1819,22 @@
  /**
   * The OID for the server-side sort request control.
   */
  public static final String OID_SERVER_SIDE_SORT_REQUEST_CONTROL =
       "1.2.840.113556.1.4.473";
  /**
   * The OID for the server-side sort response control.
   */
  public static final String OID_SERVER_SIDE_SORT_RESPONSE_CONTROL =
       "1.2.840.113556.1.4.474";
  /**
   * The IANA-assigned OID for the feature allowing the use of LDAP true and
   * false filters.
   */
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java
New file
@@ -0,0 +1,730 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SortKey;
import org.opends.server.types.SortOrder;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class contains a number of test cases for the server side sort request
 * and response controls.
 */
public class ServerSideSortControlTestCase
    extends ControlsTestCase
{
  // The givenName attribute type.
  private AttributeType givenNameType;
  // The sn attribute type.
  private AttributeType snType;
  // The DN for "Aaccf Johnson"
  DN aaccfJohnsonDN;
  // The DN for "Aaron Zimmerman"
  DN aaronZimmermanDN;
  // The DN for "Albert Smith"
  DN albertSmithDN;
  // The DN for "Albert Zimmerman"
  DN albertZimmermanDN;
  // The DN for "lowercase mcgee"
  DN lowercaseMcGeeDN;
  // The DN for "Mararet Jones"
  DN margaretJonesDN;
  // The DN for "Mary Jones"
  DN maryJonesDN;
  // The DN for "Sam Zweck"
  DN samZweckDN;
  // The DN for "Zorro"
  DN zorroDN;
  /**
   * Make sure that the server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    givenNameType = DirectoryServer.getAttributeType("givenname", false);
    assertNotNull(givenNameType);
    snType = DirectoryServer.getAttributeType("sn", false);
    assertNotNull(snType);
    aaccfJohnsonDN    = DN.decode("uid=aaccf.johnson,dc=example,dc=com");
    aaronZimmermanDN  = DN.decode("uid=aaron.zimmerman,dc=example,dc=com");
    albertSmithDN     = DN.decode("uid=albert.smith,dc=example,dc=com");
    albertZimmermanDN = DN.decode("uid=albert.zimmerman,dc=example,dc=com");
    lowercaseMcGeeDN  = DN.decode("uid=lowercase.mcgee,dc=example,dc=com");
    margaretJonesDN   = DN.decode("uid=margaret.jones,dc=example,dc=com");
    maryJonesDN       = DN.decode("uid=mary.jones,dc=example,dc=com");
    samZweckDN        = DN.decode("uid=sam.zweck,dc=example,dc=com");
    zorroDN           = DN.decode("uid=zorro,dc=example,dc=com");
  }
  /**
   * Populates the JE DB with a set of test data.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void populateDB()
          throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman",
      "",
      "dn: uid=mary.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: mary.jones",
      "givenName: Mary",
      "sn: Jones",
      "cn: Mary Jones",
      "",
      "dn: uid=margaret.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: margaret.jones",
      "givenName: Margaret",
      "givenName: Maggie",
      "sn: Jones",
      "sn: Smith",
      "cn: Maggie Jones-Smith",
      "",
      "dn: uid=aaccf.johnson,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: aaccf.johnson",
      "givenName: Aaccf",
      "sn: Johnson",
      "cn: Aaccf Johnson",
      "",
      "dn: uid=sam.zweck,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: sam.zweck",
      "givenName: Sam",
      "sn: Zweck",
      "cn: Sam Zweck",
      "",
      "dn: uid=lowercase.mcgee,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: lowercase.mcgee",
      "givenName: lowercase",
      "sn: mcgee",
      "cn: lowercase mcgee",
      "",
      "dn: uid=zorro,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: zorro",
      "sn: Zorro",
      "cn: Zorro"
    );
  }
  /**
   * Tests the first constructor for the request control with different sort
   * order values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor1()
              throws Exception
  {
    SortKey sortKey = new SortKey(givenNameType, true);
    SortOrder sortOrder = new SortOrder(sortKey);
    new ServerSideSortRequestControl(sortOrder).toString();
    sortKey.toString();
    sortOrder.toString();
    sortKey = new SortKey(givenNameType, false);
    sortOrder = new SortOrder(sortKey);
    new ServerSideSortRequestControl(sortOrder).toString();
    sortKey.toString();
    sortOrder.toString();
    SortKey[] sortKeys =
    {
      new SortKey(snType, true),
      new SortKey(givenNameType, true)
    };
    sortOrder = new SortOrder(sortKeys);
    new ServerSideSortRequestControl(sortOrder).toString();
    sortOrder.toString();
  }
  /**
   * Tests the second constructor for the request control with different sort
   * order strings.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor2()
              throws Exception
  {
    new ServerSideSortRequestControl("givenName").toString();
    new ServerSideSortRequestControl("givenName:caseIgnoreOrderingMatch").
             toString();
    new ServerSideSortRequestControl("+givenName").toString();
    new ServerSideSortRequestControl("-givenName").toString();
    new ServerSideSortRequestControl("givenName,sn").toString();
    new ServerSideSortRequestControl("givenName,+sn").toString();
    new ServerSideSortRequestControl("givenName,-sn").toString();
    new ServerSideSortRequestControl("+givenName,sn").toString();
    new ServerSideSortRequestControl("+givenName,+sn").toString();
    new ServerSideSortRequestControl("+givenName,-sn").toString();
    new ServerSideSortRequestControl("-givenName").toString();
    new ServerSideSortRequestControl("-givenName,+sn").toString();
    new ServerSideSortRequestControl("-givenName,-sn").toString();
    new ServerSideSortRequestControl("-givenName,-sn:caseExactOrderingMatch").
             toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of ascending givenName values.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameAscending()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of ascending givenName values using a specific
   * ordering matching rule.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameAscendingCaseExact()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl(
                                 "givenName:caseExactOrderingMatch"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of descending givenName values.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameDescending()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("-givenName"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaronZimmermanDN);  // Zeke
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(margaretJonesDN);   // Margaret
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of descending givenName values using a specific
   * ordering matching rule.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameDescendingCaseExact()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl(
                                 "-givenName:caseExactOrderingMatch"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(aaronZimmermanDN);  // Zeke
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(margaretJonesDN);   // Margaret
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of ascending givenName and ascending sn values.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameAscendingSnAscending()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName,sn"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertSmithDN);     // Albert, lower sn
    expectedDNOrder.add(albertZimmermanDN); // Albert, higher sn
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control to
   * sort the entries in order of ascending givenName and descending sn values.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchGivenNameAscendingSnDescending()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName,-sn"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN);    // Aaccf
    expectedDNOrder.add(aaronZimmermanDN);  // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, higher sn
    expectedDNOrder.add(albertSmithDN);     // Albert, lower sn
    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
    expectedDNOrder.add(margaretJonesDN);   // Maggie
    expectedDNOrder.add(maryJonesDN);       // Mary
    expectedDNOrder.add(samZweckDN);        // Sam
    expectedDNOrder.add(zorroDN);           // No first name
    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries())
    {
      returnedDNOrder.add(e.getDN());
    }
    assertEquals(returnedDNOrder, expectedDNOrder);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 1);
    ServerSideSortResponseControl responseControl =
         ServerSideSortResponseControl.decodeControl(responseControls.get(0));
    assertEquals(responseControl.getResultCode(), 0);
    assertNull(responseControl.getAttributeType());
    responseControl.toString();
  }
  /**
   * Tests performing an internal search using the server-side sort control with
   * an undefined attribute type.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchUndefinedAttribute()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("undefined"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertFalse(internalSearch.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests performing an internal search using the server-side sort control with
   * an undefined ordering rule.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchUndefinedOrderingRule()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl(
                                 "givenName:undefinedOrderingMatch"));
    InternalSearchOperation internalSearch =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), requestControls,
                  DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=person)"),
                  null, null);
    internalSearch.run();
    assertFalse(internalSearch.getResultCode() == ResultCode.SUCCESS);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java
@@ -1692,6 +1692,587 @@
  /**
   * Tests the use of both the server-side sort control and the simple paged
   * results control.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortWithPagedResults()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman",
      "",
      "dn: uid=mary.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: mary.jones",
      "givenName: Mary",
      "sn: Jones",
      "cn: Mary Jones",
      "",
      "dn: uid=margaret.jones,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: margaret.jones",
      "givenName: Margaret",
      "givenName: Maggie",
      "sn: Jones",
      "sn: Smith",
      "cn: Maggie Jones-Smith",
      "",
      "dn: uid=aaccf.johnson,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: aaccf.johnson",
      "givenName: Aaccf",
      "sn: Johnson",
      "cn: Aaccf Johnson",
      "",
      "dn: uid=sam.zweck,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: sam.zweck",
      "givenName: Sam",
      "sn: Zweck",
      "cn: Sam Zweck",
      "",
      "dn: uid=lowercase.mcgee,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: lowercase.mcgee",
      "givenName: lowercase",
      "sn: mcgee",
      "cn: lowercase mcgee",
      "",
      "dn: uid=zorro,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: zorro",
      "sn: Zorro",
      "cn: Zorro");
    String[] pagedArgs =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName",
      "--simplePageSize", "2",
      "--countEntries",
      "(objectClass=*)"
    };
    String[] unpagedArgs =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName",
      "--countEntries",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(pagedArgs, false, null, System.err),
                 LDAPSearch.mainSearch(unpagedArgs, false, null, System.err));
  }
  /**
   * Tests the use of the server-side sort control with valid sort criteria.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortValidGivenNameAscending()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the use of the server-side sort control with valid sort criteria.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortValidGivenNameDescending()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "-givenName",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the use of the server-side sort control with valid sort criteria.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortValidGivenNameAscendingCaseExactOrderingMatch()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName:caseExactOrderingMatch",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the use of the server-side sort control with valid sort criteria.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortValidSnAscendingGivenNameAscending()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,givenName",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the use of the server-side sort control with valid sort criteria.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortValidSnAscendingGivenNameDescending()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: uid=albert.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Albert",
      "sn: Zimmerman",
      "cn: Albert Zimmerman",
      "",
      "dn: uid=albert.smith,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.smith",
      "givenName: Albert",
      "sn: Smith",
      "cn: Albert Smith",
      "",
      "dn: uid=aaron.zimmerman,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: albert.zimmerman",
      "givenName: Aaron",
      "givenName: Zeke",
      "sn: Zimmerman",
      "cn: Aaron Zimmerman");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "sn,-givenName",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Tests the use of the server-side sort control with an empty sort order.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortEmptySortOrder()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the server-side sort control with a sort order containing
   * a key with no attribute type.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortSortOrderMissingType()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "-,sn",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the server-side sort control with a sort order containing
   * a key with a colon but no matching rule.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortSortOrderMissingMatchingRule()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "-sn:",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the server-side sort control with an undefined attribute.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortUndefinedAttribute()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "undefined",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the use of the server-side sort control with an undefined ordering
   * rule.
   *
   * @throws  Exception  If an unexpectd problem occurs.
   */
  @Test()
  public void testSortUndefinedOrderingRule()
         throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-b", "dc=example,dc=com",
      "-s", "sub",
      "-S", "givenName:undefined",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the LDAPSearch tool with the "--help" option.
   */
  @Test()