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

neil_a_wilson
27.43.2007 5481111924a6e611d785bb086d42b936293c2795
opendj-sdk/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);
  }
opendj-sdk/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(),
opendj-sdk/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);
    }
  }
}
opendj-sdk/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);
  }
}
opendj-sdk/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.)
opendj-sdk/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(")");
  }
}
opendj-sdk/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(")");
  }
}
opendj-sdk/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(")");
  }
}
opendj-sdk/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");
  }
}
opendj-sdk/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");
  }
}
opendj-sdk/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");
  }
}
opendj-sdk/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,
opendj-sdk/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 @@
      }
    }
  }
}
opendj-sdk/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(")");
  }
}
opendj-sdk/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(")");
  }
}
opendj-sdk/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.
   */
opendj-sdk/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);
  }
}
opendj-sdk/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()