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

neil_a_wilson
06.39.2007 947c2fe927c772adf4a5abb555648a34a375f618
Update the way that the server handles the VLV request control to be more
forgiving with target ranges that are out of the bounds of the actual result
set. Negative target offsets will still result in errors, but a target offset
of zero will be treated as if it were one. If beforeCount is greater than or
equal to the target offset, then it will now be truncated. If the target
offset or assertion value is beyond the end of the result set, then only the
before count entries will be returned and the target position will be set to
one greater than the content count.

OpenDS Issue Number: 1907
2 files modified
217 ■■■■ changed files
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java 50 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java 167 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
@@ -131,10 +131,10 @@
      if (vlvRequest.getTargetType() == VLVRequestControl.TYPE_TARGET_BYOFFSET)
      {
        int targetOffset = vlvRequest.getOffset();
        int listOffset = targetOffset - 1; // VLV offsets start at 1, not 0.
        int startPos = listOffset - beforeCount;
        if (startPos < 0)
        if (targetOffset < 0)
        {
          // The client specified a negative target offset.  This should never
          // be allowed.
          searchOperation.addResponseControl(
               new VLVResponseControl(targetOffset, sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
@@ -144,17 +144,32 @@
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
        }
        else if (targetOffset == 0)
        {
          // This is an easy mistake to make, since VLV offsets start at 1
          // instead of 0.  We'll assume the client meant to use 1.
          targetOffset = 1;
        }
        int listOffset = targetOffset - 1; // VLV offsets start at 1, not 0.
        int startPos = listOffset - beforeCount;
        if (startPos < 0)
        {
          // This can happen if beforeCount >= offset, and in this case we'll
          // just adjust the start position to ignore the range of beforeCount
          // that doesn't exist.
          startPos    = 0;
          beforeCount = listOffset;
        }
        else if (startPos >= sortMap.size())
        {
          searchOperation.addResponseControl(
               new VLVResponseControl(targetOffset, sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
          int    msgID   = MSGID_ENTRYIDSORTER_OFFSET_TOO_LARGE;
          String message = getMessage(msgID, vlvRequest.getOffset(),
                                      sortMap.size());
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
          // The start position is beyond the end of the list.  In this case,
          // we'll assume that the start position was one greater than the
          // size of the list and will only return the beforeCount entries.
          targetOffset = sortMap.size() + 1;
          listOffset   = sortMap.size();
          startPos     = listOffset - beforeCount;
          afterCount   = 0;
        }
        int count = 1 + beforeCount + afterCount;
@@ -253,14 +268,9 @@
        if (! targetFound)
        {
          searchOperation.addResponseControl(
               new VLVResponseControl(sortMap.size(), sortMap.size(),
                                      LDAPResultCode.OFFSET_RANGE_ERROR));
          int    msgID   = MSGID_ENTRYIDSORTER_TARGET_VALUE_NOT_FOUND;
          String message = getMessage(msgID);
          throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
                                       message, msgID);
          // No entry was found to be greater than or equal to the sort key, so
          // the target offset will be one greater than the content count.
          targetOffset = sortMap.size() + 1;
        }
        sortedIDs = new long[listSize];
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
@@ -469,12 +469,12 @@
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an offset of zero.
   * subset of the entries using an offset of one.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetZeroOffset()
  public void testInternalSearchByOffsetOneOffset()
         throws Exception
  {
    populateDB();
@@ -546,13 +546,91 @@
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using a nonzero offset that still is completely
   * within the bounds of the result set.
   * subset of the entries using an offset of zero, which should be treated like
   * an offset of one.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetNonZeroOffset()
  public void testInternalSearchByOffsetZeroOffset()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, 0, 0));
    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
    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(), 2);
    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl            vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
      {
        sortResponse = ServerSideSortResponseControl.decodeControl(c);
      }
      else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
      else
      {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }
    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a
   * subset of the entries using an offset that isn't at the beginning of the
   * result set but is still completely within the bounds of that set.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetThreeOffset()
         throws Exception
  {
    populateDB();
@@ -624,7 +702,58 @@
  /**
   * Tests performing an internal search using the VLV control with a negative
   * start position.
   * target offset.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetNegativeOffset()
         throws Exception
  {
    populateDB();
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, -1, 0));
    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();
    // It will be successful because it's not a critical control.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    VLVResponseControl vlvResponse  = null;
    for (Control c : responseControls)
    {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
      {
        vlvResponse = VLVResponseControl.decodeControl(c);
      }
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
  }
  /**
   * Tests performing an internal search using the VLV control with an offset of
   * one but a beforeCount that puts the start position at a negative value.
   *
   * @throws  Exception  If an unexpected problem occurred.
   */
@@ -667,8 +796,7 @@
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
  }
@@ -702,9 +830,21 @@
    internalSearch.run();
    // It will be successful because it's not a critical control.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    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);
@@ -718,8 +858,9 @@
    }
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
@@ -1164,7 +1305,9 @@
    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(),
                 LDAPResultCode.OFFSET_RANGE_ERROR);
                 LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
}