/*
|
* 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
|
*
|
*
|
* Copyright 2008 Sun Microsystems, Inc.
|
* Portions Copyright 2011 ForgeRock AS
|
*/
|
package org.opends.server.backends.jeb;
|
import org.opends.messages.Message;
|
|
|
|
import java.util.Map;
|
import java.util.Iterator;
|
import java.util.LinkedList;
|
import java.util.TreeMap;
|
|
import org.opends.server.controls.VLVRequestControl;
|
import org.opends.server.controls.VLVResponseControl;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.core.SearchOperation;
|
import org.opends.server.protocols.ldap.LDAPResultCode;
|
import org.opends.server.types.*;
|
|
import static org.opends.messages.JebMessages.*;
|
import static org.opends.server.util.StaticUtils.*;
|
import com.sleepycat.je.LockMode;
|
|
|
/**
|
* 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.
|
* @param vlvRequest The VLV request control included in the search
|
* request, or {@code null} if there was none.
|
*
|
* @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,
|
VLVRequestControl vlvRequest)
|
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, LockMode.DEFAULT);
|
|
if ((! e.matchesBaseAndScope(baseDN, scope)) ||
|
(! filter.matchesEntry(e)))
|
{
|
continue;
|
}
|
|
sortMap.put(new SortValues(id, e, sortOrder), id);
|
}
|
catch (Exception e)
|
{
|
Message message = ERR_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY.get(
|
String.valueOf(id), getExceptionMessage(e));
|
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
|
message, e);
|
}
|
}
|
|
|
// See if there is a VLV request to further pare down the set of results,
|
// and if there is where it should be processed by offset or assertion
|
// value.
|
long[] sortedIDs;
|
if (vlvRequest != null)
|
{
|
int beforeCount = vlvRequest.getBeforeCount();
|
int afterCount = vlvRequest.getAfterCount();
|
|
if (vlvRequest.getTargetType() == VLVRequestControl.TYPE_TARGET_BYOFFSET)
|
{
|
int targetOffset = vlvRequest.getOffset();
|
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));
|
|
Message message = ERR_ENTRYIDSORTER_NEGATIVE_START_POS.get();
|
throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR,
|
message);
|
}
|
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())
|
{
|
// 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;
|
sortedIDs = new long[count];
|
|
int treePos = 0;
|
int arrayPos = 0;
|
Iterator<EntryID> idIterator = sortMap.values().iterator();
|
while (idIterator.hasNext())
|
{
|
EntryID id = idIterator.next();
|
if (treePos++ < startPos)
|
{
|
continue;
|
}
|
|
sortedIDs[arrayPos++] = id.longValue();
|
if (arrayPos >= count)
|
{
|
break;
|
}
|
}
|
|
if (arrayPos < count)
|
{
|
// We don't have enough entries in the set to meet the requested
|
// page size, so we'll need to shorten the array.
|
long[] newIDArray = new long[arrayPos];
|
System.arraycopy(sortedIDs, 0, newIDArray, 0, arrayPos);
|
sortedIDs = newIDArray;
|
}
|
|
searchOperation.addResponseControl(
|
new VLVResponseControl(targetOffset, sortMap.size(),
|
LDAPResultCode.SUCCESS));
|
}
|
else
|
{
|
AttributeValue assertionValue =
|
AttributeValues.create(
|
sortOrder.getSortKeys()[0].getAttributeType(),
|
vlvRequest.getGreaterThanOrEqualAssertion());
|
|
boolean targetFound = false;
|
int targetOffset = 0;
|
int includedBeforeCount = 0;
|
int includedAfterCount = 0;
|
int listSize = 0;
|
LinkedList<EntryID> idList = new LinkedList<EntryID>();
|
Iterator<Map.Entry<SortValues,EntryID>> mapIterator =
|
sortMap.entrySet().iterator();
|
while (mapIterator.hasNext())
|
{
|
Map.Entry<SortValues,EntryID> entry = mapIterator.next();
|
SortValues sortValues = entry.getKey();
|
EntryID id = entry.getValue();
|
|
if (targetFound)
|
{
|
idList.add(id);
|
listSize++;
|
includedAfterCount++;
|
if (includedAfterCount >= afterCount)
|
{
|
break;
|
}
|
}
|
else
|
{
|
targetFound = (sortValues.compareTo(assertionValue) >= 0);
|
targetOffset++;
|
|
if (targetFound)
|
{
|
idList.add(id);
|
listSize++;
|
}
|
else if (beforeCount > 0)
|
{
|
idList.add(id);
|
includedBeforeCount++;
|
if (includedBeforeCount > beforeCount)
|
{
|
idList.removeFirst();
|
includedBeforeCount--;
|
}
|
else
|
{
|
listSize++;
|
}
|
}
|
}
|
}
|
|
if (! targetFound)
|
{
|
// 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];
|
Iterator<EntryID> idIterator = idList.iterator();
|
for (int i=0; i < listSize; i++)
|
{
|
sortedIDs[i] = idIterator.next().longValue();
|
}
|
|
searchOperation.addResponseControl(
|
new VLVResponseControl(targetOffset, sortMap.size(),
|
LDAPResultCode.SUCCESS));
|
}
|
}
|
else
|
{
|
sortedIDs = new long[sortMap.size()];
|
int i=0;
|
for (EntryID id : sortMap.values())
|
{
|
sortedIDs[i++] = id.longValue();
|
}
|
}
|
|
return new EntryIDSet(sortedIDs, 0, sortedIDs.length);
|
}
|
}
|