From 21e188d6ae36425c2c01c3fe7fcf15241cc725c1 Mon Sep 17 00:00:00 2001
From: boli <boli@localhost>
Date: Fri, 27 Jul 2007 21:06:35 +0000
Subject: [PATCH] These set of changes implement VLV and filter capability to OpenDS:

---
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java                         |  536 +++-
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java                              |  306 ++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportJob.java                              |   32 
 opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java                                |   31 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml                 |   16 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java                     |  193 +
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexBuilder.java                        |  324 ++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java                           |   52 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/RebuildJob.java                             |   65 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndex.java                               | 1451 ++++++++++++
 opendj-sdk/opends/resource/config/config.ldif                                                           |    5 
 opendj-sdk/opends/resource/admin/abbreviations.xsl                                                      |    2 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexMergeThread.java                    |  481 ++++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java                       |  352 +++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java                           |  353 --
 opendj-sdk/opends/src/server/org/opends/server/tools/VerifyIndex.java                                   |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestRebuildJob.java |   13 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVLVIndex.java   | 1105 +++++++++
 opendj-sdk/opends/src/server/org/opends/server/types/SortOrder.java                                     |   60 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java                          |  647 +++++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java                            |   10 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VLVJEIndexConfiguration.xml                |  219 +
 opendj-sdk/opends/src/server/org/opends/server/types/SortKey.java                                       |   89 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java                             |   38 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/State.java                                  |   64 
 opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif                                  |   66 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java  |   92 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/AttributeIndexBuilder.java                  |  386 +++
 opendj-sdk/opends/resource/schema/02-config.ldif                                                        |   25 
 29 files changed, 6,411 insertions(+), 606 deletions(-)

diff --git a/opendj-sdk/opends/resource/admin/abbreviations.xsl b/opendj-sdk/opends/resource/admin/abbreviations.xsl
index eaa6876..163f76c 100644
--- a/opendj-sdk/opends/resource/admin/abbreviations.xsl
+++ b/opendj-sdk/opends/resource/admin/abbreviations.xsl
@@ -48,7 +48,7 @@
               or $value = 'jdbc' or $value = 'tcp' or $value = 'tls'
               or $value = 'pkcs11' or $value = 'sasl' or $value = 'gssapi'
               or $value = 'md5' or $value = 'je' or $value = 'dse'
-              or $value = 'fifo'
+              or $value = 'fifo' or $value= 'vlv'
              "/>
   </xsl:template>
 </xsl:stylesheet>
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index d3d2df8..a62d9ff 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -223,6 +223,11 @@
 ds-cfg-index-attribute: entryuuid
 ds-cfg-index-type: equality
 
+dn: cn=VLV Index,ds-cfg-backend-id=userRoot,cn=Backends,cn=config
+objectClass: top
+objectClass: ds-cfg-branch
+cn: VLV Index
+
 dn: ds-cfg-backend-id=backup,cn=Backends,cn=config
 objectClass: top
 objectClass: ds-cfg-backend
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 5fb22e9..174bc97 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -1475,6 +1475,24 @@
   NAME 'ds-cfg-strip-syntax-minimum-upper-bound'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.437
+  NAME 'ds-cfg-vlv-je-index-base-dn'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.438
+  NAME 'ds-cfg-vlv-je-index-scope'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.439
+  NAME 'ds-cfg-vlv-je-index-filter'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.440
+  NAME 'ds-cfg-vlv-je-index-sort-order'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.441
+  NAME 'ds-cfg-vlv-je-index-name'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.442
+  NAME 'ds-cfg-vlv-je-index-maximum-block-size'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.443
   NAME 'ds-cfg-state-update-failure-policy'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
@@ -2108,6 +2126,13 @@
   MAY ( ds-cfg-default-user-password-storage-scheme $
   ds-cfg-default-auth-password-storage-scheme )
   X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.117
+  NAME 'ds-cfg-vlv-je-index' SUP top STRUCTURAL
+  MUST ( ds-cfg-vlv-je-index-base-dn $ ds-cfg-vlv-je-index-scope $
+         ds-cfg-vlv-je-index-filter $ ds-cfg-vlv-je-index-sort-order $
+         ds-cfg-vlv-je-index-name )
+  MAY  ( ds-cfg-vlv-je-index-maximum-block-size )
+  X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.118
   NAME 'ds-cfg-smtp-alert-handler' SUP ds-cfg-alert-handler STRUCTURAL
   MUST ( ds-cfg-sender-address $ ds-cfg-recipient-address $
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
index fb9e639..e799fbe 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
@@ -66,6 +66,22 @@
       </cli:relation>
     </adm:profile>
   </adm:relation>
+  <adm:relation name="vlv-je-index">
+    <adm:one-to-many naming-property="vlv-index-name"/>
+     <adm:profile name="ldap">
+      <ldap:rdn-sequence>
+        cn=VLV Index
+      </ldap:rdn-sequence>
+    </adm:profile>
+    <adm:profile name="cli">
+      <cli:relation>
+        <cli:default-property name="vlv-index-base-dn" />
+        <cli:default-property name="vlv-index-scope" />
+        <cli:default-property name="vlv-index-filter" />
+        <cli:default-property name="vlv-index-sort-order" />
+      </cli:relation>
+    </adm:profile>
+  </adm:relation>
   <adm:property-override name="backend-class">
     <adm:default-behavior>
       <adm:defined>
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VLVJEIndexConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VLVJEIndexConfiguration.xml
new file mode 100644
index 0000000..fe0824c
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VLVJEIndexConfiguration.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ! 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.
+ ! -->
+
+<adm:managed-object name="vlv-je-index" plural-name="vlv-je-indexes"
+  package="org.opends.server.admin.std"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The <adm:user-friendly-plural-name/> are used to store information about
+    a specific search request that makes it possible to efficiently process
+    them using the VLV control.
+  </adm:synopsis>
+  <adm:description>
+    A VLV index effectively notifies the server that a virtual list view, with
+    specific query and sort parameters, will be performed. This index also
+    allows the server to collect and maintain the information required to make
+    using the virtual list view faster.
+  </adm:description>
+  <adm:tag name="database" />
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:oid>1.3.6.1.4.1.26027.1.2.117</ldap:oid>
+      <ldap:name>ds-cfg-vlv-je-index</ldap:name>
+      <ldap:superior>top</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property name="vlv-index-base-dn"
+                mandatory="true"
+                multi-valued="false">
+    <adm:synopsis>
+      This specifies the base DN used in the search query being indexed.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+          The index will need to be rebuilt after this modifying this
+          property.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+    <adm:syntax>
+      <adm:dn/>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.437</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-base-dn</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="vlv-index-scope"
+                mandatory="true"
+                multi-valued="false">
+    <adm:synopsis>
+      This specifies the LDAP scope of the query being indexed.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+          The index will need to be rebuilt after this modifying this
+          property.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+    <adm:syntax>
+      <adm:enumeration>
+        <adm:value name="base-level">
+          <adm:synopsis>
+            Search the base object only.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="single-object">
+          <adm:synopsis>
+            Search subordinate objects to the base object but not include
+            the base object itself.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="subordinate-subtree">
+          <adm:synopsis>
+            Search the entire subtree below the base object but not include
+            the base object itself.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="whole-subtree">
+          <adm:synopsis>
+            Search the base object and the entire subtree below the base
+            object.
+          </adm:synopsis>
+        </adm:value>
+      </adm:enumeration>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.438</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-scope </ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="vlv-index-filter"
+                mandatory="true"
+                multi-valued="false">
+    <adm:synopsis>
+      This specifies the LDAP filter used in the query being indexed.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+          The index will need to be rebuilt after this modifying this
+          property.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.439</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-filter</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="vlv-index-sort-order"
+                mandatory="true"
+                multi-valued="false">
+    <adm:synopsis>
+      This specifies the names of attributes to sort the entries for the query
+      being indexed.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+         The index will need to be rebuilt after this modifying this
+          property.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.440</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-sort-order</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="vlv-index-name"
+                mandatory="true"
+                multi-valued="false"
+                read-only="true">
+    <adm:synopsis>
+      This specifies a unique name for this VLV index.
+    </adm:synopsis>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.441</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-name</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="vlv-index-sorted-set-capacity"
+                mandatory="false"
+                multi-valued="false"
+                read-only="true">
+    <adm:synopsis>
+      This specifies the number of entry IDs to store in a single
+      sorted set before it must be split.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          4000
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:integer>
+        <adm:unit-synopsis>
+          Number of entry IDs
+        </adm:unit-synopsis>
+      </adm:integer>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.442</ldap:oid>
+        <ldap:name>ds-cfg-vlv-je-index-maximum-block-size</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+</adm:managed-object>
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/AttributeIndexBuilder.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/AttributeIndexBuilder.java
new file mode 100644
index 0000000..e3c20ab
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/AttributeIndexBuilder.java
@@ -0,0 +1,386 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.Entry;
+import static org.opends.server.util.StaticUtils.getFileForPath;
+
+import com.sleepycat.je.DatabaseException;
+import com.sleepycat.je.Transaction;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.io.ByteArrayOutputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class is used to create an attribute index for an import process.
+ * It is used as follows.
+ * <pre>
+ * startProcessing();
+ * processEntry(entry);
+ * processEntry(entry);
+ * ...
+ * stopProcessing();
+ * merge();
+ * </pre>
+ */
+public class AttributeIndexBuilder implements IndexBuilder
+{
+  /**
+   * The import context.
+   */
+  private ImportContext importContext;
+
+  /**
+   * The index database.
+   */
+  private Index index;
+
+  /**
+   * The indexer to generate the index keys.
+   */
+  private Indexer indexer;
+
+  /**
+   * The write buffer.
+   */
+  ArrayList<IndexMod> buffer;
+
+  /**
+   * The write buffer size.
+   */
+  private int bufferSize;
+
+  /**
+   * Current output file number.
+   */
+  private int fileNumber = 0;
+
+  /**
+   * The index entry limit.
+   */
+  private int entryLimit;
+
+  /**
+   * A unique prefix for temporary files to prevent conflicts.
+   */
+  private String fileNamePrefix;
+
+  /**
+   * Indicates whether we are replacing existing data or not.
+   */
+  private boolean replaceExisting = false;
+
+
+  private ByteArrayOutputStream addBytesStream = new ByteArrayOutputStream();
+  private ByteArrayOutputStream delBytesStream = new ByteArrayOutputStream();
+
+  private DataOutputStream addBytesDataStream;
+  private DataOutputStream delBytesDataStream;
+
+  /**
+   * A file name filter to identify temporary files we have written.
+   */
+  private FilenameFilter filter = new FilenameFilter()
+  {
+    public boolean accept(File d, String name)
+    {
+      return name.startsWith(fileNamePrefix);
+    }
+  };
+
+  /**
+   * Construct an index builder.
+   *
+   * @param importContext The import context.
+   * @param index The index database we are writing.
+   * @param entryLimit The index entry limit.
+   * @param bufferSize The amount of memory available for buffering.
+   */
+  public AttributeIndexBuilder(ImportContext importContext,
+                      Index index, int entryLimit, long bufferSize)
+  {
+    this.importContext = importContext;
+    this.index = index;
+    this.indexer = index.indexer;
+    this.entryLimit = entryLimit;
+    this.bufferSize = (int)bufferSize/100;
+    long tid = Thread.currentThread().getId();
+    fileNamePrefix = index.getName() + "_" + tid + "_";
+    replaceExisting =
+         importContext.getLDIFImportConfig().appendToExistingData() &&
+         importContext.getLDIFImportConfig().replaceExistingEntries();
+    addBytesDataStream = new DataOutputStream(addBytesStream);
+    delBytesDataStream = new DataOutputStream(delBytesStream);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void startProcessing()
+  {
+    // Clean up any work files left over from a previous run.
+    File tempDir = getFileForPath(
+        importContext.getConfig().getBackendImportTempDirectory());
+    File[] files = tempDir.listFiles(filter);
+    if (files != null)
+    {
+      for (File f : files)
+      {
+        f.delete();
+      }
+    }
+
+    buffer = new ArrayList<IndexMod>(bufferSize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void processEntry(Entry oldEntry, Entry newEntry, EntryID entryID)
+       throws DatabaseException, IOException
+  {
+    Transaction txn = null;
+
+    // Update the index for this entry.
+    if (oldEntry != null)
+    {
+      // This is an entry being replaced.
+      Set<ASN1OctetString> addKeys = new HashSet<ASN1OctetString>();
+      Set<ASN1OctetString> delKeys = new HashSet<ASN1OctetString>();
+
+      indexer.replaceEntry(txn, oldEntry, newEntry, addKeys, delKeys);
+
+      for (ASN1OctetString k : delKeys)
+      {
+        removeID(k.value(), entryID);
+      }
+
+      for (ASN1OctetString k : addKeys)
+      {
+        insertID(k.value(), entryID);
+      }
+    }
+    else
+    {
+      // This is a new entry.
+      Set<ASN1OctetString> addKeys = new HashSet<ASN1OctetString>();
+      indexer.indexEntry(txn, newEntry, addKeys);
+      for (ASN1OctetString k : addKeys)
+      {
+        insertID(k.value(), entryID);
+      }
+    }
+
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void stopProcessing() throws IOException
+  {
+    flushBuffer();
+  }
+
+
+
+  /**
+   * Get a statistic of the number of keys that reached the entry limit.
+   *
+   * @return The number of keys that reached the entry limit.
+   */
+  public int getEntryLimitExceededCount()
+  {
+    return index.getEntryLimitExceededCount();
+  }
+
+  /**
+   * Record the insertion of an entry ID.
+   * @param key The index key.
+   * @param entryID The entry ID.
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void insertID(byte[] key, EntryID entryID)
+       throws IOException
+  {
+    if (buffer.size() >= bufferSize)
+    {
+      flushBuffer();
+    }
+
+    IndexMod kav = new IndexMod(key, entryID, false);
+    buffer.add(kav);
+  }
+
+  /**
+   * Record the deletion of an entry ID.
+   * @param key The index key.
+   * @param entryID The entry ID.
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void removeID(byte[] key, EntryID entryID)
+       throws IOException
+  {
+    if (buffer.size() >= bufferSize)
+    {
+      flushBuffer();
+    }
+
+    IndexMod kav = new IndexMod(key, entryID, true);
+    buffer.add(kav);
+  }
+
+  /**
+   * Called when the buffer is full. It first sorts the buffer using the same
+   * key comparator used by the index database. Then it merges all the
+   * IDs for the same key together and writes each key and its list of IDs
+   * to an intermediate binary file.
+   * A list of deleted IDs is only present if we are replacing existing entries.
+   *
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void flushBuffer() throws IOException
+  {
+    if (buffer.size() == 0)
+    {
+      return;
+    }
+
+    // Keys must be sorted before we can merge duplicates.
+    IndexModComparator comparator;
+    if (replaceExisting)
+    {
+      // The entry IDs may be out of order.
+      // We must sort by key and ID.
+      comparator = new IndexModComparator(indexer.getComparator(), true);
+    }
+    else
+    {
+      // The entry IDs are all new and are therefore already ordered.
+      // We just need to sort by key.
+      comparator = new IndexModComparator(indexer.getComparator(), false);
+    }
+    Collections.sort(buffer, comparator);
+
+    // Start a new file.
+    fileNumber++;
+    String fileName = fileNamePrefix + String.valueOf(fileNumber);
+    File file = new File(getFileForPath(
+        importContext.getConfig().getBackendImportTempDirectory()),
+                         fileName);
+    BufferedOutputStream bufferedStream =
+         new BufferedOutputStream(new FileOutputStream(file));
+    DataOutputStream dataStream = new DataOutputStream(bufferedStream);
+
+    // Reset the byte array output streams but preserve the underlying arrays.
+    addBytesStream.reset();
+    delBytesStream.reset();
+
+    try
+    {
+      byte[] currentKey = null;
+      for (IndexMod key : buffer)
+      {
+        byte[] keyString = key.key;
+        if (!Arrays.equals(keyString,currentKey))
+        {
+          if (currentKey != null)
+          {
+            dataStream.writeInt(currentKey.length);
+            dataStream.write(currentKey);
+            dataStream.writeInt(addBytesStream.size());
+            addBytesStream.writeTo(dataStream);
+            if (replaceExisting)
+            {
+              dataStream.writeInt(delBytesStream.size());
+              delBytesStream.writeTo(dataStream);
+            }
+          }
+
+          currentKey = keyString;
+          addBytesStream.reset();
+          delBytesStream.reset();
+        }
+
+        if (key.isDelete)
+        {
+          delBytesDataStream.writeLong(key.value.longValue());
+        }
+        else
+        {
+          addBytesDataStream.writeLong(key.value.longValue());
+        }
+
+      }
+
+      if (currentKey != null)
+      {
+        dataStream.writeInt(currentKey.length);
+        dataStream.write(currentKey);
+        dataStream.writeInt(addBytesStream.size());
+        addBytesStream.writeTo(dataStream);
+        if (replaceExisting)
+        {
+          dataStream.writeInt(delBytesStream.size());
+          delBytesStream.writeTo(dataStream);
+        }
+      }
+
+      buffer = new ArrayList<IndexMod>(bufferSize);
+    }
+    finally
+    {
+      dataStream.close();
+    }
+  }
+
+  /**
+   * Get a string that identifies this index builder.
+   *
+   * @return A string that identifies this index builder.
+   */
+  public String toString()
+  {
+    return indexer.toString();
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
index bcf83cb..aabc606 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -906,6 +906,16 @@
       }
       throw createDirectoryException(e);
     }
+    catch (JebException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION, e.getMessage());
+      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                   message, MSGID_JEB_DATABASE_EXCEPTION);
+    }
     finally
     {
       ec.sharedLock.unlock();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 69fb221..8113b85 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -59,6 +59,7 @@
 import static org.opends.server.util.ServerConstants.*;
 import org.opends.server.admin.std.server.JEBackendCfg;
 import org.opends.server.admin.std.server.JEIndexCfg;
+import org.opends.server.admin.std.server.VLVJEIndexCfg;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.server.ConfigurationAddListener;
 import org.opends.server.admin.server.ConfigurationDeleteListener;
@@ -70,9 +71,7 @@
  * the guts of the backend API methods for LDAP operations.
  */
 public class EntryContainer
-    implements ConfigurationChangeListener<JEBackendCfg>,
-    ConfigurationAddListener<JEIndexCfg>,
-    ConfigurationDeleteListener<JEIndexCfg>
+    implements ConfigurationChangeListener<JEBackendCfg>
 {
   /**
    * The tracer object for the debug logger.
@@ -116,6 +115,16 @@
   public static final String ATTR_DEBUG_SEARCH_INDEX = "debugsearchindex";
 
   /**
+   * The attribute index configuration manager.
+   */
+  public AttributeJEIndexCfgManager attributeJEIndexCfgManager;
+
+  /**
+   * The vlv index configuration manager.
+   */
+  public VLVJEIndexCfgManager vlvJEIndexCfgManager;
+
+  /**
    * The backend to which this entry entryContainer belongs.
    */
   private Backend backend;
@@ -176,6 +185,11 @@
   private HashMap<AttributeType, AttributeIndex> attrIndexMap;
 
   /**
+   * The set of VLV indexes.
+   */
+  private HashMap<String, VLVIndex> vlvIndexMap;
+
+  /**
    * Cached value from config so they don't have to be retrieved per operation.
    */
   private int deadlockRetryLimit;
@@ -188,6 +202,196 @@
 
   private String databasePrefix;
   /**
+   * This class is responsible for managing the configuraiton for attribute
+   * indexes used within this entry container.
+   */
+  public class AttributeJEIndexCfgManager implements
+      ConfigurationAddListener<JEIndexCfg>,
+      ConfigurationDeleteListener<JEIndexCfg>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isConfigurationAddAcceptable(JEIndexCfg cfg,
+                                               List<String> unacceptableReasons)
+    {
+      // TODO: validate more before returning true?
+      return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationAdd(JEIndexCfg cfg)
+    {
+      ConfigChangeResult ccr;
+      boolean adminActionRequired = false;
+      ArrayList<String> messages = new ArrayList<String>();
+
+      try
+      {
+        AttributeIndex index =
+            new AttributeIndex(cfg, state, env, EntryContainer.this);
+        index.open();
+        attrIndexMap.put(cfg.getIndexAttribute(), index);
+      }
+      catch(Exception e)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(e));
+        ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
+                                     adminActionRequired,
+                                     messages);
+        return ccr;
+      }
+
+      adminActionRequired = true;
+      int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
+      messages.add(getMessage(msgID, cfg.getIndexAttribute().getNameOrOID()));
+      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
+                                    messages);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized boolean isConfigurationDeleteAcceptable(
+        JEIndexCfg cfg, List<String> unacceptableReasons)
+    {
+      // TODO: validate more before returning true?
+      return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationDelete(JEIndexCfg cfg)
+    {
+      ConfigChangeResult ccr;
+      boolean adminActionRequired = false;
+      ArrayList<String> messages = new ArrayList<String>();
+
+      exclusiveLock.lock();
+      try
+      {
+        AttributeIndex index = attrIndexMap.get(cfg.getIndexAttribute());
+        deleteAttributeIndex(index);
+        attrIndexMap.remove(cfg.getIndexAttribute());
+      }
+      catch(DatabaseException de)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(de));
+        ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
+                                     adminActionRequired,
+                                     messages);
+        return ccr;
+      }
+      finally
+      {
+        exclusiveLock.unlock();
+      }
+
+      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
+                                    messages);
+    }
+  }
+
+  /**
+   * This class is responsible for managing the configuraiton for VLV indexes
+   * used within this entry container.
+   */
+  public class VLVJEIndexCfgManager implements
+      ConfigurationAddListener<VLVJEIndexCfg>,
+      ConfigurationDeleteListener<VLVJEIndexCfg>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isConfigurationAddAcceptable(
+        VLVJEIndexCfg cfg, List<String> unacceptableReasons)
+    {
+      // TODO: validate more before returning true?
+      return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationAdd(VLVJEIndexCfg cfg)
+    {
+      ConfigChangeResult ccr;
+      boolean adminActionRequired = false;
+      ArrayList<String> messages = new ArrayList<String>();
+
+      try
+      {
+        VLVIndex vlvIndex = new VLVIndex(cfg, state, env, EntryContainer.this);
+        vlvIndex.open();
+        vlvIndexMap.put(cfg.getVLVIndexName().toLowerCase(), vlvIndex);
+      }
+      catch(Exception e)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(e));
+        ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
+                                     adminActionRequired,
+                                     messages);
+        return ccr;
+      }
+
+      adminActionRequired = true;
+      int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
+      messages.add(getMessage(msgID, cfg.getVLVIndexName()));
+      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
+                                    messages);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isConfigurationDeleteAcceptable(VLVJEIndexCfg cfg,
+                                               List<String> unacceptableReasons)
+    {
+      // TODO: validate more before returning true?
+      return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationDelete(VLVJEIndexCfg cfg)
+    {
+      ConfigChangeResult ccr;
+      boolean adminActionRequired = false;
+      ArrayList<String> messages = new ArrayList<String>();
+
+      exclusiveLock.lock();
+      try
+      {
+        VLVIndex vlvIndex =
+            vlvIndexMap.get(cfg.getVLVIndexName().toLowerCase());
+        vlvIndex.close();
+        deleteDatabase(vlvIndex);
+        vlvIndexMap.remove(cfg.getVLVIndexName());
+      }
+      catch(DatabaseException de)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(de));
+        ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
+                                     adminActionRequired,
+                                     messages);
+        return ccr;
+      }
+      finally
+      {
+        exclusiveLock.unlock();
+      }
+
+      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
+                                    messages);
+    }
+
+  }
+
+  /**
    * A read write lock to handle schema changes and bulk changes.
    */
   private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@@ -243,9 +447,20 @@
     // Instantiate the attribute indexes.
     attrIndexMap = new HashMap<AttributeType, AttributeIndex>();
 
+    // Instantiate the VLV indexes.
+    vlvIndexMap = new HashMap<String, VLVIndex>();
+
     config.addJEChangeListener(this);
-    config.addJEIndexAddListener(this);
-    config.addJEIndexDeleteListener(this);
+
+    attributeJEIndexCfgManager =
+        new AttributeJEIndexCfgManager();
+    config.addJEIndexAddListener(attributeJEIndexCfgManager);
+    config.addJEIndexDeleteListener(attributeJEIndexCfgManager);
+
+    vlvJEIndexCfgManager =
+        new VLVJEIndexCfgManager();
+    config.addVLVJEIndexAddListener(vlvJEIndexCfgManager);
+    config.addVLVJEIndexDeleteListener(vlvJEIndexCfgManager);
   }
 
   /**
@@ -298,6 +513,15 @@
         index.open();
         attrIndexMap.put(indexCfg.getIndexAttribute(), index);
       }
+
+      for(String idx : config.listVLVJEIndexes())
+      {
+        VLVJEIndexCfg vlvIndexCfg = config.getVLVJEIndex(idx);
+
+        VLVIndex vlvIndex = new VLVIndex(vlvIndexCfg, state, env, this);
+        vlvIndex.open();
+        vlvIndexMap.put(vlvIndexCfg.getVLVIndexName().toLowerCase(), vlvIndex);
+      }
     }
     catch (DatabaseException de)
     {
@@ -326,8 +550,10 @@
     }
 
     config.removeJEChangeListener(this);
-    config.removeJEIndexAddListener(this);
-    config.removeJEIndexDeleteListener(this);
+    config.removeJEIndexAddListener(attributeJEIndexCfgManager);
+    config.removeJEIndexDeleteListener(attributeJEIndexCfgManager);
+    config.removeVLVJEIndexDeleteListener(vlvJEIndexCfgManager);
+    config.removeVLVJEIndexDeleteListener(vlvJEIndexCfgManager);
   }
 
   /**
@@ -397,6 +623,17 @@
   }
 
   /**
+   * Look for an VLV index for the given index name.
+   *
+   * @param vlvIndexName The vlv index name for which an vlv index is needed.
+   * @return The VLV index or null if there is none with that name.
+   */
+  public VLVIndex getVLVIndex(String vlvIndexName)
+  {
+    return vlvIndexMap.get(vlvIndexName);
+  }
+
+  /**
    * Retrieve all attribute indexes.
    *
    * @return All attribute indexes defined in this entry container.
@@ -406,6 +643,15 @@
     return attrIndexMap.values();
   }
 
+  /**
+   * Retrieve all VLV indexes.
+   *
+   * @return The collection of VLV indexes defined in this entry container.
+   */
+  public Collection<VLVIndex> getVLVIndexes()
+  {
+    return vlvIndexMap.values();
+  }
 
   /**
    * Determine the highest entryID in the entryContainer.
@@ -448,9 +694,10 @@
    *          If a problem occurs while processing the
    *          search.
    * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
    */
   public void search(SearchOperation searchOperation)
-       throws DirectoryException, DatabaseException
+       throws DirectoryException, DatabaseException, JebException
   {
     DN baseDN = searchOperation.getBaseDN();
     SearchScope searchScope = searchOperation.getScope();
@@ -617,54 +864,115 @@
       debugBuffer = new StringBuilder();
     }
 
-    // Create an index filter to get the search result candidate entries.
-    IndexFilter indexFilter =
-         new IndexFilter(this, searchOperation, debugBuffer);
-
-    // Evaluate the filter against the attribute indexes.
-    EntryIDSet entryIDList = indexFilter.evaluate();
-
-    // Evaluate the search scope against the id2children and id2subtree indexes.
+    EntryIDSet entryIDList = null;
     boolean candidatesAreInScope = false;
-    if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD)
+    if(sortRequest != null)
     {
-      // Read the ID from dn2id.
-      EntryID baseID = dn2id.get(null, baseDN);
-      if (baseID == null)
+      for(VLVIndex vlvIndex : vlvIndexMap.values())
       {
-        int messageID = MSGID_JEB_SEARCH_NO_SUCH_OBJECT;
-        String message = getMessage(messageID, baseDN.toString());
-        DN matchedDN = getMatchedDN(baseDN);
-        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
-            message, messageID, matchedDN, null);
-      }
-      DatabaseEntry baseIDData = baseID.getDatabaseEntry();
-
-      EntryIDSet scopeList;
-      if (searchScope == SearchScope.SINGLE_LEVEL)
-      {
-        scopeList = id2children.readKey(baseIDData, null, LockMode.DEFAULT);
-      }
-      else
-      {
-        scopeList = id2subtree.readKey(baseIDData, null, LockMode.DEFAULT);
-        if (searchScope == SearchScope.WHOLE_SUBTREE)
+        try
         {
-          // The id2subtree list does not include the base entry ID.
-          scopeList.add(baseID);
+          entryIDList =
+              vlvIndex.evaluate(null, searchOperation, sortRequest, vlvRequest,
+                                debugBuffer);
+          if(entryIDList != null)
+          {
+            searchOperation.addResponseControl(
+                new ServerSideSortResponseControl(LDAPResultCode.SUCCESS,
+                                                  null));
+            candidatesAreInScope = true;
+            break;
+          }
+        }
+        catch (DirectoryException de)
+        {
+          searchOperation.addResponseControl(
+              new ServerSideSortResponseControl(
+                  de.getResultCode().getIntValue(), null));
+
+          if (sortRequest.isCritical())
+          {
+            throw de;
+          }
         }
       }
-      entryIDList.retainAll(scopeList);
-      if (debugBuffer != null)
+    }
+
+    if(entryIDList == null)
+    {
+      // Create an index filter to get the search result candidate entries.
+      IndexFilter indexFilter =
+          new IndexFilter(this, searchOperation, debugBuffer);
+
+      // Evaluate the filter against the attribute indexes.
+      entryIDList = indexFilter.evaluate();
+
+      // Evaluate the search scope against the id2children and id2subtree
+      // indexes.
+      if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD)
       {
-        debugBuffer.append(" scope=");
-        debugBuffer.append(searchScope);
-        scopeList.toString(debugBuffer);
+        // Read the ID from dn2id.
+        EntryID baseID = dn2id.get(null, baseDN);
+        if (baseID == null)
+        {
+          int messageID = MSGID_JEB_SEARCH_NO_SUCH_OBJECT;
+          String message = getMessage(messageID, baseDN.toString());
+          DN matchedDN = getMatchedDN(baseDN);
+          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
+              message, messageID, matchedDN, null);
+        }
+        DatabaseEntry baseIDData = baseID.getDatabaseEntry();
+
+        EntryIDSet scopeList;
+        if (searchScope == SearchScope.SINGLE_LEVEL)
+        {
+          scopeList = id2children.readKey(baseIDData, null, LockMode.DEFAULT);
+        }
+        else
+        {
+          scopeList = id2subtree.readKey(baseIDData, null, LockMode.DEFAULT);
+          if (searchScope == SearchScope.WHOLE_SUBTREE)
+          {
+            // The id2subtree list does not include the base entry ID.
+            scopeList.add(baseID);
+          }
+        }
+        entryIDList.retainAll(scopeList);
+        if (debugBuffer != null)
+        {
+          debugBuffer.append(" scope=");
+          debugBuffer.append(searchScope);
+          scopeList.toString(debugBuffer);
+        }
+        if (scopeList.isDefined())
+        {
+          // In this case we know that every candidate is in scope.
+          candidatesAreInScope = true;
+        }
       }
-      if (scopeList.isDefined())
+
+      if (sortRequest != null)
       {
-        // In this case we know that every candidate is in scope.
-        candidatesAreInScope = true;
+        try
+        {
+          entryIDList = EntryIDSetSorter.sort(this, entryIDList,
+                                              searchOperation,
+                                              sortRequest.getSortOrder(),
+                                              vlvRequest);
+          searchOperation.addResponseControl(
+              new ServerSideSortResponseControl(LDAPResultCode.SUCCESS, null));
+        }
+        catch (DirectoryException de)
+        {
+          searchOperation.addResponseControl(
+              new ServerSideSortResponseControl(
+                  de.getResultCode().getIntValue(), null));
+
+          if (sortRequest.isCritical())
+          {
+            throw de;
+          }
+        }
       }
     }
 
@@ -697,30 +1005,6 @@
 
     if (entryIDList.isDefined())
     {
-      if (sortRequest != null)
-      {
-        try
-        {
-          entryIDList = EntryIDSetSorter.sort(this, entryIDList,
-                                              searchOperation,
-                                              sortRequest.getSortOrder(),
-                                              vlvRequest);
-          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);
     }
@@ -3313,6 +3597,11 @@
     {
       index.addEntry(txn, entryID, entry);
     }
+
+    for (VLVIndex vlvIndex : vlvIndexMap.values())
+    {
+      vlvIndex.addEntry(txn, entryID, entry);
+    }
   }
 
   /**
@@ -3332,6 +3621,11 @@
     {
       index.removeEntry(txn, entryID, entry);
     }
+
+    for (VLVIndex vlvIndex : vlvIndexMap.values())
+    {
+      vlvIndex.removeEntry(txn, entryID, entry);
+    }
   }
 
   /**
@@ -3344,11 +3638,13 @@
    * @param entryID The ID of the entry that was changed.
    * @param mods The sequence of modifications made to the entry.
    * @throws DatabaseException If an error occurs in the JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws JebException If an error occurs in the JE backend.
    */
   private void indexModifications(Transaction txn, Entry oldEntry,
                                   Entry newEntry,
                                   EntryID entryID, List<Modification> mods)
-      throws DatabaseException
+      throws DatabaseException, DirectoryException, JebException
   {
     // Process in index configuration order.
     for (AttributeIndex index : attrIndexMap.values())
@@ -3370,6 +3666,11 @@
         index.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
       }
     }
+
+    for(VLVIndex vlvIndex : vlvIndexMap.values())
+    {
+      vlvIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
+    }
   }
 
   /**
@@ -3417,6 +3718,11 @@
     {
       index.listDatabases(dbList);
     }
+
+    for (VLVIndex vlvIndex : vlvIndexMap.values())
+    {
+      dbList.add(vlvIndex);
+    }
   }
 
   /**
@@ -3861,92 +4167,6 @@
   }
 
   /**
-   * {@inheritDoc}
-   */
-  public synchronized boolean isConfigurationAddAcceptable(
-      JEIndexCfg cfg, List<String> unacceptableReasons)
-  {
-    // TODO: validate more before returning true?
-    return true;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public synchronized ConfigChangeResult applyConfigurationAdd(JEIndexCfg cfg)
-  {
-    ConfigChangeResult ccr;
-    boolean adminActionRequired = false;
-    ArrayList<String> messages = new ArrayList<String>();
-
-    try
-    {
-      AttributeIndex index =
-          new AttributeIndex(cfg, state, env, this);
-      index.open();
-      attrIndexMap.put(cfg.getIndexAttribute(), index);
-    }
-    catch(Exception e)
-    {
-      messages.add(StaticUtils.stackTraceToSingleLineString(e));
-      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
-                                   adminActionRequired,
-                                   messages);
-      return ccr;
-    }
-
-    adminActionRequired = true;
-    int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
-    messages.add(getMessage(msgID, cfg.getIndexAttribute().getNameOrOID()));
-    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
-                                  messages);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public synchronized boolean isConfigurationDeleteAcceptable(
-      JEIndexCfg cfg, List<String> unacceptableReasons)
-  {
-    // TODO: validate more before returning true?
-    return true;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public synchronized ConfigChangeResult applyConfigurationDelete(
-      JEIndexCfg cfg)
-  {
-    ConfigChangeResult ccr;
-    boolean adminActionRequired = false;
-    ArrayList<String> messages = new ArrayList<String>();
-
-    exclusiveLock.lock();
-    try
-    {
-      AttributeIndex index = attrIndexMap.get(cfg.getIndexAttribute());
-      deleteAttributeIndex(index);
-      attrIndexMap.remove(cfg.getIndexAttribute());
-    }
-    catch(DatabaseException de)
-    {
-      messages.add(StaticUtils.stackTraceToSingleLineString(de));
-      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
-                                   adminActionRequired,
-                                   messages);
-      return ccr;
-    }
-    finally
-    {
-      exclusiveLock.unlock();
-    }
-
-    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
-                                  messages);
-  }
-
-  /**
    * Get the environment config of the JE environment used in this entry
    * container.
    *
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportJob.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
index b06c2c2..67cc08e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
@@ -359,6 +359,9 @@
 
       ArrayList<IndexMergeThread> mergers = new ArrayList<IndexMergeThread>();
 
+      ArrayList<VLVIndexMergeThread> vlvIndexMergeThreads =
+        new ArrayList<VLVIndexMergeThread>();
+
       // Create merge threads for each base DN.
       for (ImportContext importContext : importMap.values())
       {
@@ -420,6 +423,14 @@
           }
         }
 
+        for(VLVIndex vlvIndex : entryContainer.getVLVIndexes())
+        {
+          VLVIndexMergeThread vlvIndexMergeThread =
+              new VLVIndexMergeThread(config, ldifImportConfig, vlvIndex);
+          vlvIndexMergeThread.setUncaughtExceptionHandler(this);
+          vlvIndexMergeThreads.add(vlvIndexMergeThread);
+        }
+
         // Id2Children index.
         Index id2Children = entryContainer.getID2Children();
         IndexMergeThread indexMergeThread =
@@ -444,6 +455,10 @@
       {
         imt.start();
       }
+      for (VLVIndexMergeThread imt : vlvIndexMergeThreads)
+      {
+        imt.start();
+      }
 
       // Wait for the threads to finish.
       for (IndexMergeThread imt : mergers)
@@ -460,6 +475,21 @@
           }
         }
       }
+      // Wait for the threads to finish.
+      for (VLVIndexMergeThread imt : vlvIndexMergeThreads)
+      {
+        try
+        {
+          imt.join();
+        }
+        catch (InterruptedException e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+        }
+      }
 
       long mergeEndTime = System.currentTimeMillis();
 
@@ -977,7 +1007,7 @@
     threads.remove(t);
     int msgID = MSGID_JEB_IMPORT_THREAD_EXCEPTION;
     String msg = getMessage(msgID, t.getName(),
-                            StaticUtils.stackTraceToSingleLineString(e));
+                        StaticUtils.stackTraceToSingleLineString(e.getCause()));
     logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR, msg,
              msgID);
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java
index fc9b757..264464b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java
@@ -170,6 +170,8 @@
       }
     }
 
+    nIndexes += entryContainer.getVLVIndexes().size();
+
     // Divide the total buffer size by the number of threads
     // and give that much to each index.
     long indexBufferSize = importContext.getBufferSize() / nIndexes;
@@ -186,64 +188,72 @@
 
       if (attrIndex.equalityIndex != null)
       {
-        IndexBuilder indexBuilder =
-             new IndexBuilder(importContext,
+        AttributeIndexBuilder attributeIndexBuilder =
+             new AttributeIndexBuilder(importContext,
                               attrIndex.equalityIndex,
                               indexEntryLimit,
                               indexBufferSize);
-        builders.add(indexBuilder);
+        builders.add(attributeIndexBuilder);
       }
       if (attrIndex.presenceIndex != null)
       {
-        IndexBuilder indexBuilder =
-             new IndexBuilder(importContext,
+        AttributeIndexBuilder attributeIndexBuilder =
+             new AttributeIndexBuilder(importContext,
                               attrIndex.presenceIndex,
                               indexEntryLimit,
                               indexBufferSize);
-        builders.add(indexBuilder);
+        builders.add(attributeIndexBuilder);
       }
       if (attrIndex.substringIndex != null)
       {
-        IndexBuilder indexBuilder =
-             new IndexBuilder(importContext,
+        AttributeIndexBuilder attributeIndexBuilder =
+             new AttributeIndexBuilder(importContext,
                               attrIndex.substringIndex,
                               indexEntryLimit,
                               indexBufferSize);
-        builders.add(indexBuilder);
+        builders.add(attributeIndexBuilder);
       }
       if (attrIndex.orderingIndex != null)
       {
-        IndexBuilder indexBuilder =
-             new IndexBuilder(importContext, attrIndex.orderingIndex,
+        AttributeIndexBuilder attributeIndexBuilder =
+             new AttributeIndexBuilder(importContext, attrIndex.orderingIndex,
                               indexEntryLimit,
                               indexBufferSize);
-        builders.add(indexBuilder);
+        builders.add(attributeIndexBuilder);
       }
       if (attrIndex.approximateIndex != null)
       {
-        IndexBuilder indexBuilder =
-            new IndexBuilder(importContext, attrIndex.approximateIndex,
+        AttributeIndexBuilder attributeIndexBuilder =
+            new AttributeIndexBuilder(importContext, attrIndex.approximateIndex,
                              indexEntryLimit,
                              indexBufferSize);
-        builders.add(indexBuilder);
+        builders.add(attributeIndexBuilder);
       }
     }
 
+    // Create an vlvIndex builder for each VLV index database.
+    for (VLVIndex vlvIndex : entryContainer.getVLVIndexes())
+    {
+      VLVIndexBuilder vlvIndexBuilder =
+          new VLVIndexBuilder(importContext, vlvIndex, indexBufferSize);
+      builders.add(vlvIndexBuilder);
+    }
+
     // Create an index builder for the children index.
     Index id2Children = entryContainer.getID2Children();
-    IndexBuilder indexBuilder =
-         new IndexBuilder(importContext, id2Children,
+    AttributeIndexBuilder attributeIndexBuilder =
+         new AttributeIndexBuilder(importContext, id2Children,
                           importContext.getConfig().getBackendIndexEntryLimit(),
                           indexBufferSize);
-    builders.add(indexBuilder);
+    builders.add(attributeIndexBuilder);
 
     // Create an index builder for the subtree index.
     Index id2Subtree = entryContainer.getID2Subtree();
-    indexBuilder =
-         new IndexBuilder(importContext, id2Subtree,
+    attributeIndexBuilder =
+         new AttributeIndexBuilder(importContext, id2Subtree,
                           importContext.getConfig().getBackendIndexEntryLimit(),
                           indexBufferSize);
-    builders.add(indexBuilder);
+    builders.add(attributeIndexBuilder);
 
     for (IndexBuilder b : builders)
     {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java
index 50e00e5..035993b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java
@@ -26,149 +26,23 @@
  */
 package org.opends.server.backends.jeb;
 
-import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.types.Entry;
-import static org.opends.server.util.StaticUtils.getFileForPath;
-
+import org.opends.server.types.DirectoryException;
 import com.sleepycat.je.DatabaseException;
-import com.sleepycat.je.Transaction;
 
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.io.ByteArrayOutputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FilenameFilter;
-import java.io.FileOutputStream;
 import java.io.IOException;
 
 /**
- * This class is used to create an attribute index for an import process.
- * It is used as follows.
- * <pre>
- * startProcessing();
- * processEntry(entry);
- * processEntry(entry);
- * ...
- * stopProcessing();
- * merge();
- * </pre>
+ * The interface that represents a index builder for the import process.
  */
-public class IndexBuilder
+public interface IndexBuilder
 {
   /**
-   * The import context.
-   */
-  private ImportContext importContext;
-
-  /**
-   * The index database.
-   */
-  private Index index;
-
-  /**
-   * The indexer to generate the index keys.
-   */
-  private Indexer indexer;
-
-  /**
-   * The write buffer.
-   */
-  ArrayList<IndexMod> buffer;
-
-  /**
-   * The write buffer size.
-   */
-  private int bufferSize;
-
-  /**
-   * Current output file number.
-   */
-  private int fileNumber = 0;
-
-  /**
-   * The index entry limit.
-   */
-  private int entryLimit;
-
-  /**
-   * A unique prefix for temporary files to prevent conflicts.
-   */
-  private String fileNamePrefix;
-
-  /**
-   * Indicates whether we are replacing existing data or not.
-   */
-  private boolean replaceExisting = false;
-
-
-  private ByteArrayOutputStream addBytesStream = new ByteArrayOutputStream();
-  private ByteArrayOutputStream delBytesStream = new ByteArrayOutputStream();
-
-  private DataOutputStream addBytesDataStream;
-  private DataOutputStream delBytesDataStream;
-
-  /**
-   * A file name filter to identify temporary files we have written.
-   */
-  private FilenameFilter filter = new FilenameFilter()
-  {
-    public boolean accept(File d, String name)
-    {
-      return name.startsWith(fileNamePrefix);
-    }
-  };
-
-  /**
-   * Construct an index builder.
-   *
-   * @param importContext The import context.
-   * @param index The index database we are writing.
-   * @param entryLimit The index entry limit.
-   * @param bufferSize The amount of memory available for buffering.
-   */
-  public IndexBuilder(ImportContext importContext,
-                      Index index, int entryLimit, long bufferSize)
-  {
-    this.importContext = importContext;
-    this.index = index;
-    this.indexer = index.indexer;
-    this.entryLimit = entryLimit;
-    this.bufferSize = (int)bufferSize/100;
-    long tid = Thread.currentThread().getId();
-    fileNamePrefix = index.getName() + "_" + tid + "_";
-    replaceExisting =
-         importContext.getLDIFImportConfig().appendToExistingData() &&
-         importContext.getLDIFImportConfig().replaceExistingEntries();
-    addBytesDataStream = new DataOutputStream(addBytesStream);
-    delBytesDataStream = new DataOutputStream(delBytesStream);
-  }
-
-  /**
    * This method must be called before this object can process any
    * entries. It cleans up any temporary files left over from a
    * previous import.
    */
-  public void startProcessing()
-  {
-    // Clean up any work files left over from a previous run.
-    File tempDir = getFileForPath(
-        importContext.getConfig().getBackendImportTempDirectory());
-    File[] files = tempDir.listFiles(filter);
-    if (files != null)
-    {
-      for (File f : files)
-      {
-        f.delete();
-      }
-    }
-
-    buffer = new ArrayList<IndexMod>(bufferSize);
-  }
+  void startProcessing();
 
   /**
    * Indicates that the index thread should process the provided entry.
@@ -176,222 +50,19 @@
    * a new entry.
    * @param newEntry The new contents of the entry.
    * @param entryID The entry ID.
-   * @throws DatabaseException If an error occurs in the JE database.
-   * @throws IOException If an I/O error occurs while writing an intermediate
-   * file.
+   * @throws com.sleepycat.je.DatabaseException If an error occurs in the JE
+   * database.
+   * @throws java.io.IOException If an I/O error occurs while writing an
+   * intermediate file.
+   * @throws DirectoryException If an error occurs while processing the entry.
    */
-  public void processEntry(Entry oldEntry, Entry newEntry, EntryID entryID)
-       throws DatabaseException, IOException
-  {
-    Transaction txn = null;
-
-    // Update the index for this entry.
-    if (oldEntry != null)
-    {
-      // This is an entry being replaced.
-      Set<ASN1OctetString> addKeys = new HashSet<ASN1OctetString>();
-      Set<ASN1OctetString> delKeys = new HashSet<ASN1OctetString>();
-
-      indexer.replaceEntry(txn, oldEntry, newEntry, addKeys, delKeys);
-
-      for (ASN1OctetString k : delKeys)
-      {
-        removeID(k.value(), entryID);
-      }
-
-      for (ASN1OctetString k : addKeys)
-      {
-        insertID(k.value(), entryID);
-      }
-    }
-    else
-    {
-      // This is a new entry.
-      Set<ASN1OctetString> addKeys = new HashSet<ASN1OctetString>();
-      indexer.indexEntry(txn, newEntry, addKeys);
-      for (ASN1OctetString k : addKeys)
-      {
-        insertID(k.value(), entryID);
-      }
-    }
-
-  }
-
-
+  void processEntry(Entry oldEntry, Entry newEntry, EntryID entryID)
+      throws DatabaseException, IOException, DirectoryException;
 
   /**
    * Indicates that there will be no more updates.
    * @throws IOException If an I/O error occurs while writing an intermediate
    * file.
    */
-  public void stopProcessing() throws IOException
-  {
-    flushBuffer();
-  }
-
-
-
-  /**
-   * Get a statistic of the number of keys that reached the entry limit.
-   *
-   * @return The number of keys that reached the entry limit.
-   */
-  public int getEntryLimitExceededCount()
-  {
-    return index.getEntryLimitExceededCount();
-  }
-
-  /**
-   * Record the insertion of an entry ID.
-   * @param key The index key.
-   * @param entryID The entry ID.
-   * @throws IOException If an I/O error occurs while writing an intermediate
-   * file.
-   */
-  private void insertID(byte[] key, EntryID entryID)
-       throws IOException
-  {
-    if (buffer.size() >= bufferSize)
-    {
-      flushBuffer();
-    }
-
-    IndexMod kav = new IndexMod(key, entryID, false);
-    buffer.add(kav);
-  }
-
-  /**
-   * Record the deletion of an entry ID.
-   * @param key The index key.
-   * @param entryID The entry ID.
-   * @throws IOException If an I/O error occurs while writing an intermediate
-   * file.
-   */
-  private void removeID(byte[] key, EntryID entryID)
-       throws IOException
-  {
-    if (buffer.size() >= bufferSize)
-    {
-      flushBuffer();
-    }
-
-    IndexMod kav = new IndexMod(key, entryID, true);
-    buffer.add(kav);
-  }
-
-  /**
-   * Called when the buffer is full. It first sorts the buffer using the same
-   * key comparator used by the index database. Then it merges all the
-   * IDs for the same key together and writes each key and its list of IDs
-   * to an intermediate binary file.
-   * A list of deleted IDs is only present if we are replacing existing entries.
-   *
-   * @throws IOException If an I/O error occurs while writing an intermediate
-   * file.
-   */
-  private void flushBuffer() throws IOException
-  {
-    if (buffer.size() == 0)
-    {
-      return;
-    }
-
-    // Keys must be sorted before we can merge duplicates.
-    IndexModComparator comparator;
-    if (replaceExisting)
-    {
-      // The entry IDs may be out of order.
-      // We must sort by key and ID.
-      comparator = new IndexModComparator(indexer.getComparator(), true);
-    }
-    else
-    {
-      // The entry IDs are all new and are therefore already ordered.
-      // We just need to sort by key.
-      comparator = new IndexModComparator(indexer.getComparator(), false);
-    }
-    Collections.sort(buffer, comparator);
-
-    // Start a new file.
-    fileNumber++;
-    String fileName = fileNamePrefix + String.valueOf(fileNumber);
-    File file = new File(getFileForPath(
-        importContext.getConfig().getBackendImportTempDirectory()),
-                         fileName);
-    BufferedOutputStream bufferedStream =
-         new BufferedOutputStream(new FileOutputStream(file));
-    DataOutputStream dataStream = new DataOutputStream(bufferedStream);
-
-    // Reset the byte array output streams but preserve the underlying arrays.
-    addBytesStream.reset();
-    delBytesStream.reset();
-
-    try
-    {
-      byte[] currentKey = null;
-      for (IndexMod key : buffer)
-      {
-        byte[] keyString = key.key;
-        if (!Arrays.equals(keyString,currentKey))
-        {
-          if (currentKey != null)
-          {
-            dataStream.writeInt(currentKey.length);
-            dataStream.write(currentKey);
-            dataStream.writeInt(addBytesStream.size());
-            addBytesStream.writeTo(dataStream);
-            if (replaceExisting)
-            {
-              dataStream.writeInt(delBytesStream.size());
-              delBytesStream.writeTo(dataStream);
-            }
-          }
-
-          currentKey = keyString;
-          addBytesStream.reset();
-          delBytesStream.reset();
-        }
-
-        if (key.isDelete)
-        {
-          delBytesDataStream.writeLong(key.value.longValue());
-        }
-        else
-        {
-          addBytesDataStream.writeLong(key.value.longValue());
-        }
-
-      }
-
-      if (currentKey != null)
-      {
-        dataStream.writeInt(currentKey.length);
-        dataStream.write(currentKey);
-        dataStream.writeInt(addBytesStream.size());
-        addBytesStream.writeTo(dataStream);
-        if (replaceExisting)
-        {
-          dataStream.writeInt(delBytesStream.size());
-          delBytesStream.writeTo(dataStream);
-        }
-      }
-
-      buffer = new ArrayList<IndexMod>(bufferSize);
-    }
-    finally
-    {
-      dataStream.close();
-    }
-  }
-
-  /**
-   * Get a string that identifies this index builder.
-   *
-   * @return A string that identifies this index builder.
-   */
-  public String toString()
-  {
-    return indexer.toString();
-  }
+  void stopProcessing() throws IOException;
 }
-
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
index 2937a90..61400bf 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
@@ -71,6 +71,11 @@
   AttributeIndex attrIndex = null;
 
   /**
+   * The VLV index to rebuild.
+   */
+  VLVIndex vlvIndex = null;
+
+  /**
    * The indexType to rebuild.
    */
   Index index = null;
@@ -112,7 +117,7 @@
    */
   enum IndexType
   {
-    DN2ID, DN2URI, ID2CHILDREN, ID2SUBTREE, INDEX, ATTRIBUTEINDEX
+    DN2ID, DN2URI, ID2CHILDREN, ID2SUBTREE, INDEX, ATTRIBUTEINDEX, VLVINDEX
   }
 
   /**
@@ -161,6 +166,21 @@
   }
 
   /**
+   * Construct a new index rebuild thread to rebuild an VLV index.
+   *
+   * @param ec The entry container to rebuild in.
+   * @param vlvIndex The VLV index to rebuild.
+   */
+  IndexRebuildThread(EntryContainer ec, VLVIndex vlvIndex)
+  {
+    super("Index Rebuild Thread " + vlvIndex.getName());
+    this.ec = ec;
+    this.indexType = IndexType.VLVINDEX;
+    this.vlvIndex = vlvIndex;
+    this.id2entry = ec.getID2Entry();
+  }
+
+  /**
    * Clear the database and prep it for the rebuild.
    *
    * @throws DatabaseException if a JE databse error occurs while clearing
@@ -202,6 +222,18 @@
       return;
     }
 
+    if(indexType == IndexType.VLVINDEX && vlvIndex == null)
+    {
+      //TODO: throw error
+      if(debugEnabled())
+      {
+        TRACER.debugError("No VLV index specified. Rebuild process " +
+            "terminated.");
+      }
+
+      return;
+    }
+
     switch(indexType)
     {
       case DN2ID :
@@ -222,6 +254,10 @@
         ec.clearAttributeIndex(attrIndex);
         attrIndex.setRebuildStatus(true);
         break;
+      case VLVINDEX :
+        ec.clearDatabase(vlvIndex);
+        vlvIndex.setRebuildStatus(true);
+        break;
       case INDEX :
         ec.clearDatabase(index);
         index.setRebuildStatus(true);
@@ -267,6 +303,18 @@
       return;
     }
 
+    if(indexType == IndexType.VLVINDEX && vlvIndex == null)
+    {
+      //TODO: throw error
+      if(debugEnabled())
+      {
+        TRACER.debugError("No VLV index specified. Rebuild process " +
+            "terminated.");
+      }
+
+      return;
+    }
+
     try
     {
       totalEntries = getTotalEntries();
@@ -283,6 +331,8 @@
           break;
         case ATTRIBUTEINDEX : rebuildAttributeIndex(attrIndex);
           break;
+        case VLVINDEX : rebuildVLVIndex(vlvIndex);
+          break;
         case INDEX : rebuildAttributeIndex(index);
       }
 
@@ -325,7 +375,7 @@
 
     //Iterate through the id2entry database and insert associated dn2id
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -337,15 +387,14 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
           Entry entry = JebFormat.entryFromDatabase(data.getData());
 
-          //TODO: Should we add all records in a big transaction?
-          //TODO: Should we make each insert a transaction?
           // Insert into dn2id.
-          if (dn2id.insert(null, entry.getDN(), entryID))
+          if (dn2id.insert(txn, entry.getDN(), entryID))
           {
             rebuiltEntries++;
           }
@@ -363,13 +412,12 @@
                         entry.getDN().toString(), entryID.longValue());
             }
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: throw error stating that the indexType could be in an
-          // inconsistant state.
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
@@ -410,7 +458,7 @@
 
     //Iterate through the id2entry database and insert associated dn2uri
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -423,15 +471,14 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
           Entry entry = JebFormat.entryFromDatabase(data.getData());
 
-          //TODO: Should we add all records in a big transaction?
-          //TODO: Should we make each insert a transaction?
           // Insert into dn2uri.
-          if (dn2uri.addEntry(null, entry))
+          if (dn2uri.addEntry(txn, entry))
           {
             rebuiltEntries++;
           }
@@ -449,13 +496,12 @@
                         entry.getDN().toString(), entryID.longValue());
             }
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: throw error stating that the indexType could be in an
-          // inconsistant state.
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
@@ -500,7 +546,7 @@
 
     //Iterate through the id2entry database and insert associated dn2children
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -512,6 +558,7 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
@@ -525,11 +572,11 @@
             dn2uri.targetEntryReferrals(entry.getDN(), null);
 
             // Read the parent ID from dn2id.
-            EntryID parentID = dn2id.get(null, parentDN);
+            EntryID parentID = dn2id.get(txn, parentDN);
             if (parentID != null)
             {
               // Insert into id2children for parent ID.
-              if(id2children.insertID(null, parentID.getDatabaseEntry(),
+              if(id2children.insertID(txn, parentID.getDatabaseEntry(),
                                       entryID))
               {
                 rebuiltEntries++;
@@ -561,11 +608,12 @@
           {
             skippedEntries++;
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
@@ -612,7 +660,7 @@
 
     //Iterate through the id2entry database and insert associated dn2subtree
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -624,6 +672,7 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
@@ -639,11 +688,11 @@
             dn2uri.targetEntryReferrals(entry.getDN(), null);
 
             // Read the parent ID from dn2id.
-            EntryID parentID = dn2id.get(null, parentDN);
+            EntryID parentID = dn2id.get(txn, parentDN);
             if (parentID != null)
             {
               // Insert into id2subtree for parent ID.
-              if(!id2subtree.insertID(null, parentID.getDatabaseEntry(),
+              if(!id2subtree.insertID(txn, parentID.getDatabaseEntry(),
                                       entryID))
               {
                 success = false;
@@ -703,11 +752,12 @@
           {
             skippedEntries++;
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
@@ -749,7 +799,7 @@
 
     //Iterate through the id2entry database and insert associated indexType
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -761,13 +811,14 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
           Entry entry = JebFormat.entryFromDatabase(data.getData());
 
           // Insert into attribute indexType.
-          if(index.addEntry(null, entryID, entry))
+          if(index.addEntry(txn, entryID, entry))
           {
             rebuiltEntries++;
           }
@@ -786,11 +837,12 @@
                         entry.getDN().toString(), entryID.longValue());
             }
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
@@ -815,6 +867,87 @@
   }
 
   /**
+   * Rebuild the VLV index.
+   *
+   * @param vlvIndex The VLV index to rebuild.
+   * @throws DatabaseException if an error occurs during rebuild.
+   */
+  private void rebuildVLVIndex(VLVIndex vlvIndex)
+      throws DatabaseException
+  {
+
+    //Iterate through the id2entry database and insert associated indexType
+    //records.
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
+    try
+    {
+      DatabaseEntry key = new DatabaseEntry();
+      DatabaseEntry data = new DatabaseEntry();
+      LockMode lockMode = LockMode.DEFAULT;
+
+      OperationStatus status;
+      for (status = cursor.getFirst(key, data, lockMode);
+           status == OperationStatus.SUCCESS;
+           status = cursor.getNext(key, data, lockMode))
+      {
+        Transaction txn = ec.beginTransaction();
+        try
+        {
+          EntryID entryID = new EntryID(key);
+          Entry entry = JebFormat.entryFromDatabase(data.getData());
+
+          // Insert into attribute indexType.
+          if(vlvIndex.addEntry(txn, entryID, entry))
+          {
+            rebuiltEntries++;
+          }
+          else
+          {
+            // The entry already exists in one or more entry sets.
+            // This could happen if some other process got to this entry
+            // before we did. Since the backend should be offline, this
+            // might be a problem.
+            if(debugEnabled())
+            {
+              duplicatedEntries++;
+              TRACER.debugInfo("Unable to insert entry with DN %s and ID %d " +
+                  "into the VLV index %s because it already " +
+                  "exists.",
+                        entry.getDN().toString(), entryID.longValue(),
+                        vlvIndex.getName());
+            }
+          }
+
+          EntryContainer.transactionCommit(txn);
+          processedEntries++;
+        }
+        catch (Exception e)
+        {
+          EntryContainer.transactionAbort(txn);
+          skippedEntries++;
+
+          int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
+          String message = getMessage(msgID, index.getName(),
+                                      stackTraceToSingleLineString(e));
+          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR,
+                   message, msgID);
+
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+        }
+      }
+      vlvIndex.setRebuildStatus(false);
+      vlvIndex.setTrusted(null, true);
+    }
+    finally
+    {
+      cursor.close();
+    }
+  }
+
+  /**
    * Rebuild the partial attribute index.
    *
    * @param index The indexType to rebuild.
@@ -832,7 +965,7 @@
 
     //Iterate through the id2entry database and insert associated indexType
     //records.
-    Cursor cursor = id2entry.openCursor(null, null);
+    Cursor cursor = id2entry.openCursor(null, CursorConfig.READ_COMMITTED);
     try
     {
       DatabaseEntry key = new DatabaseEntry();
@@ -844,13 +977,14 @@
            status == OperationStatus.SUCCESS;
            status = cursor.getNext(key, data, lockMode))
       {
+        Transaction txn = ec.beginTransaction();
         try
         {
           EntryID entryID = new EntryID(key);
           Entry entry = JebFormat.entryFromDatabase(data.getData());
 
           // Insert into attribute indexType.
-          if(index.addEntry(null, entryID, entry))
+          if(index.addEntry(txn, entryID, entry))
           {
             rebuiltEntries++;
           }
@@ -869,11 +1003,12 @@
                         entry.getDN().toString(), entryID.longValue());
             }
           }
+          EntryContainer.transactionCommit(txn);
           processedEntries++;
         }
         catch (Exception e)
         {
-          //TODO: Should we continue on or stop right now?
+          EntryContainer.transactionAbort(txn);
           skippedEntries++;
 
           int    msgID   = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/RebuildJob.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/RebuildJob.java
index 136572c..42adfbd 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/RebuildJob.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/RebuildJob.java
@@ -54,6 +54,8 @@
     MSGID_JEB_REBUILD_INDEX_CONFLICT;
 import static org.opends.server.messages.JebMessages.
     MSGID_JEB_REBUILD_START;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_VLV_INDEX_NOT_CONFIGURED;
 import static org.opends.server.messages.MessageHandler.getMessage;
 
 /**
@@ -270,32 +272,35 @@
     //Make sure there are no running rebuild jobs
     jobsMutex.lock();
 
-    for(RebuildJob otherJob : rebuildJobs)
+    try
     {
-      String conflictIndex =
-          job.rebuildConfig.checkConflicts(otherJob.rebuildConfig);
-      if(conflictIndex != null)
+      for(RebuildJob otherJob : rebuildJobs)
       {
-        jobsMutex.unlock();
-        //TODO: Throw error and bail out.
-        if(debugEnabled())
+        String conflictIndex =
+            job.rebuildConfig.checkConflicts(otherJob.rebuildConfig);
+        if(conflictIndex != null)
         {
-          TRACER.debugError("Conflit detected. This job config: %s, " +
-              "That job config: %s.",
-                     job.rebuildConfig, otherJob.rebuildConfig);
+          if(debugEnabled())
+          {
+            TRACER.debugError("Conflit detected. This job config: %s, " +
+                "That job config: %s.",
+                              job.rebuildConfig, otherJob.rebuildConfig);
+          }
+
+          int msgID = MSGID_JEB_REBUILD_INDEX_CONFLICT;
+          String msg = getMessage(msgID, conflictIndex);
+          throw new JebException(msgID, msg);
         }
-
-
-        int msgID = MSGID_JEB_REBUILD_INDEX_CONFLICT;
-        String msg = getMessage(msgID, conflictIndex);
-        throw new JebException(msgID, msg);
       }
+
+      //No conflicts are found. Add the job to the list of currently running
+      // jobs.
+      rebuildJobs.add(job);
     }
-
-    //No conflicts are found. Add the job to the list of currently running jobs.
-    rebuildJobs.add(job);
-
-    jobsMutex.unlock();
+    finally
+    {
+      jobsMutex.unlock();
+    }
   }
 
   private static void removeJob(RebuildJob job)
@@ -360,6 +365,26 @@
             rebuildThread = new IndexRebuildThread(entryContainer,
                                        IndexRebuildThread.IndexType.ID2SUBTREE);
           }
+          else if (lowerName.startsWith("vlv."))
+          {
+            if(lowerName.length() < 5)
+            {
+              int msgID = MSGID_JEB_VLV_INDEX_NOT_CONFIGURED;
+              String msg = getMessage(msgID, lowerName);
+              throw new JebException(msgID, msg);
+            }
+
+            VLVIndex vlvIndex =
+                entryContainer.getVLVIndex(lowerName.substring(4));
+            if(vlvIndex == null)
+            {
+              int msgID = MSGID_JEB_VLV_INDEX_NOT_CONFIGURED;
+              String msg = getMessage(msgID, lowerName.substring(4));
+              throw new JebException(msgID, msg);
+            }
+
+            rebuildThread = new IndexRebuildThread(entryContainer, vlvIndex);
+          }
           else
           {
             String[] attrIndexParts = lowerName.split("\\.");
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java
index 1d26ff8..ed2bda1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValues.java
@@ -38,7 +38,6 @@
 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
@@ -66,6 +65,22 @@
   /**
    * 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 values     The attribute values for this sort values.
+   * @param sortOrder  The sort order to use to obtain the necessary values.
+   */
+  public SortValues(EntryID entryID, AttributeValue[] values,
+                    SortOrder sortOrder)
+  {
+    this.entryID = entryID;
+    this.sortOrder = sortOrder;
+    this.values = values;
+  }
+
+  /**
+   * 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
@@ -243,5 +258,26 @@
     buffer.append(entryID.toString());
     buffer.append(")");
   }
+
+  /**
+   * Retrieve the attribute values in this sort values.
+   *
+   * @return The array of attribute values for this sort values.
+   */
+  public AttributeValue[] getValues()
+  {
+    return values;
+  }
+
+  /**
+   * Retrieve the entry ID in this sort values.
+   *
+   * @return The entry ID for this sort values.
+   */
+  public long getEntryID()
+  {
+    return entryID.longValue();
+  }
 }
 
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java
new file mode 100644
index 0000000..138db7f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java
@@ -0,0 +1,647 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import org.opends.server.types.*;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+
+import java.util.HashMap;
+
+import com.sleepycat.je.DatabaseException;
+
+/**
+ * This class representsa  partial sorted set of sorted entries in a VLV
+ * index.
+ */
+public class SortValuesSet
+{
+  private static final int ENCODED_VALUE_SIZE = 16;
+  private static final int ENCODED_VALUE_LENGTH_SIZE =
+      encodedLengthSize(ENCODED_VALUE_SIZE);
+  private static final int ENCODED_ATTRIBUTE_VALUE_SIZE =
+      ENCODED_VALUE_LENGTH_SIZE + ENCODED_VALUE_SIZE;
+
+  private ID2Entry id2entry;
+
+  private long[] entryIDs;
+
+  private byte[] valuesBytes;
+
+  private byte[] keyBytes;
+
+  private HashMap<EntryID, AttributeValue[]> cachedAttributeValues;
+
+  private VLVIndex vlvIndex;
+
+  /**
+   * Construct an empty sort values set with the given information.
+   *
+   * @param vlvIndex The VLV index using this set.
+   * @param id2entry The ID2Entry database.
+   */
+  public SortValuesSet(VLVIndex vlvIndex, ID2Entry id2entry)
+  {
+    this.keyBytes = new byte[0];
+    this.entryIDs = null;
+    this.valuesBytes = null;
+    this.id2entry = id2entry;
+    this.vlvIndex = vlvIndex;
+    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
+  }
+
+  /**
+   * Construct a sort values set from the database.
+   *
+   * @param keyBytes The database key used to locate this set.
+   * @param dataBytes The bytes to decode and construct this set.
+   * @param vlvIndex The VLV index using this set.
+   * @param id2entry The ID2Entry database.
+   */
+  public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex,
+                       ID2Entry id2entry)
+  {
+    this.keyBytes = keyBytes;
+    this.id2entry = id2entry;
+    this.vlvIndex = vlvIndex;
+    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
+
+    if(dataBytes == null)
+    {
+      entryIDs = new long[0];
+      return;
+    }
+
+    entryIDs = getEncodedIDs(dataBytes, 0);
+    valuesBytes = new byte[entryIDs.length * ENCODED_ATTRIBUTE_VALUE_SIZE *
+        vlvIndex.sortOrder.getSortKeys().length];
+    System.arraycopy(dataBytes, entryIDs.length * 8 + 4, valuesBytes, 0,
+                     valuesBytes.length);
+  }
+
+  private SortValuesSet(long[] entryIDs, byte[] keyBytes, byte[] valuesBytes,
+                        VLVIndex vlvIndex, ID2Entry id2entry)
+  {
+    this.keyBytes = keyBytes;
+    this.id2entry = id2entry;
+    this.entryIDs = entryIDs;
+    this.valuesBytes = valuesBytes;
+    this.vlvIndex = vlvIndex;
+    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
+  }
+
+  /**
+   * Add the given entryID and values from this VLV idnex.
+   *
+   * @param entryID The entry ID to add.
+   * @param values The values to add.
+   * @return True if the information was successfully added or False
+   * otherwise.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public boolean add(long entryID, AttributeValue[] values)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(values == null)
+    {
+      return false;
+    }
+
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      entryIDs = new long[1];
+      entryIDs[0] = entryID;
+      valuesBytes = attributeValuesToDatabase(values);
+      return true;
+    }
+    if(vlvIndex.comparator.compareValuesInSet(this,
+                                              entryIDs.length - 1, entryID,
+                                              values) < 0)
+    {
+      long[] updatedEntryIDs = new long[entryIDs.length + 1];
+      System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, entryIDs.length);
+      updatedEntryIDs[entryIDs.length] = entryID;
+
+      byte[] newValuesBytes = attributeValuesToDatabase(values);
+      byte[] updatedValuesBytes = new byte[valuesBytes.length +
+          newValuesBytes.length];
+      System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0,
+                       valuesBytes.length);
+      System.arraycopy(newValuesBytes, 0, updatedValuesBytes,
+                       valuesBytes.length,
+                       newValuesBytes.length);
+
+      entryIDs = updatedEntryIDs;
+      valuesBytes = updatedValuesBytes;
+      return true;
+    }
+    else
+    {
+      int pos = binarySearch(entryID, values);
+      if(pos >= 0)
+      {
+        if(entryIDs[pos] == entryID)
+        {
+          // The entry ID is alreadly present.
+          return false;
+        }
+      }
+      else
+      {
+        // For a negative return value r, the vlvIndex -(r+1) gives the array
+        // ndex at which the specified value can be inserted to maintain
+        // the sorted order of the array.
+        pos = -(pos+1);
+      }
+
+      long[] updatedEntryIDs = new long[entryIDs.length + 1];
+      System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos);
+      System.arraycopy(entryIDs, pos, updatedEntryIDs, pos+1,
+                       entryIDs.length-pos);
+      updatedEntryIDs[pos] = entryID;
+
+      byte[] newValuesBytes = attributeValuesToDatabase(values);
+      int valuesPos = pos * newValuesBytes.length;
+      byte[] updatedValuesBytes = new byte[valuesBytes.length +
+          newValuesBytes.length];
+      System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
+      System.arraycopy(valuesBytes, valuesPos,  updatedValuesBytes,
+                       valuesPos + newValuesBytes.length,
+                       valuesBytes.length - valuesPos);
+      System.arraycopy(newValuesBytes, 0, updatedValuesBytes, valuesPos,
+                       newValuesBytes.length);
+
+      entryIDs = updatedEntryIDs;
+      valuesBytes = updatedValuesBytes;
+    }
+
+    return true;
+  }
+
+  /**
+   * Remove the given entryID and values from this VLV idnex.
+   *
+   * @param entryID The entry ID to remove.
+   * @param values The values to remove.
+   * @return True if the information was successfully removed or False
+   * otherwise.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public boolean remove(long entryID, AttributeValue[] values)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      return false;
+    }
+
+    int pos = binarySearch(entryID, values);
+    if(pos < 0)
+    {
+      // Not found.
+      return false;
+    }
+    else
+    {
+      // Found it.
+      long[] updatedEntryIDs = new long[entryIDs.length - 1];
+      System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos);
+      System.arraycopy(entryIDs, pos+1, updatedEntryIDs, pos,
+                       entryIDs.length-pos-1);
+
+      int valuesLength = ENCODED_ATTRIBUTE_VALUE_SIZE *
+          vlvIndex.sortOrder.getSortKeys().length;
+      int valuesPos = pos * valuesLength;
+      byte[] updatedValuesBytes = new byte[valuesBytes.length - valuesLength];
+      System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
+      System.arraycopy(valuesBytes, valuesPos + valuesLength,
+                       updatedValuesBytes, valuesPos,
+                       valuesBytes.length - valuesPos - valuesLength);
+
+      entryIDs = updatedEntryIDs;
+      valuesBytes = updatedValuesBytes;
+      return true;
+    }
+  }
+
+  /**
+   * Split portions of this set into another set. The values of the new set is
+   * from the front of this set.
+   *
+   * @param splitLength The size of the new set.
+   * @return The split set.
+   */
+  public SortValuesSet split(int splitLength)
+  {
+    long[] splitEntryIDs = new long[splitLength];
+    byte[] splitValuesBytes = new byte[splitLength *
+        ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length];
+
+    long[] updatedEntryIDs = new long[entryIDs.length - splitEntryIDs.length];
+    System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, updatedEntryIDs.length);
+    System.arraycopy(entryIDs, updatedEntryIDs.length, splitEntryIDs, 0,
+                     splitEntryIDs.length);
+
+    byte[] updatedValuesBytes = new byte[valuesBytes.length -
+        splitValuesBytes.length];
+    System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0,
+                     updatedValuesBytes.length);
+    System.arraycopy(valuesBytes, updatedValuesBytes.length, splitValuesBytes,
+                     0, splitValuesBytes.length);
+
+    SortValuesSet splitValuesSet = new SortValuesSet(splitEntryIDs,
+                                                     keyBytes,
+                                                     splitValuesBytes,
+                                                     vlvIndex, id2entry);
+
+    entryIDs = updatedEntryIDs;
+    valuesBytes = updatedValuesBytes;
+    keyBytes = null;
+
+    return splitValuesSet;
+  }
+
+  /**
+   * Encode this set to its database format.
+   *
+   * @return The encoded bytes representing this set.
+   */
+  public byte[] toDatabase()
+  {
+    byte[] entryIDBytes = JebFormat.entryIDListToDatabase(entryIDs);
+    byte[] concatBytes = new byte[entryIDBytes.length + valuesBytes.length + 4];
+    int v = entryIDs.length;
+
+    for (int j = 3; j >= 0; j--)
+    {
+      concatBytes[j] = (byte) (v & 0xFF);
+      v >>>= 8;
+    }
+
+    System.arraycopy(entryIDBytes, 0, concatBytes, 4, entryIDBytes.length);
+    System.arraycopy(valuesBytes, 0, concatBytes, entryIDBytes.length+4,
+                     valuesBytes.length);
+
+    return concatBytes;
+  }
+
+  /**
+   * Get the size of the provided encoded set.
+   *
+   * @param bytes The encoded bytes of a SortValuesSet to decode the size from.
+   * @param offset The byte offset to start decoding.
+   * @return The size of the provided encoded set.
+   */
+  public static int getEncodedSize(byte[] bytes, int offset)
+  {
+    int v = 0;
+    for (int i = offset; i < offset + 4; i++)
+    {
+      v <<= 8;
+      v |= (bytes[i] & 0xFF);
+    }
+    return v;
+  }
+
+  /**
+   * Get the IDs from the provided encoded set.
+   *
+   * @param bytes The encoded bytes of a SortValuesSet to decode the IDs from.
+   * @param offset The byte offset to start decoding.
+   * @return The decoded IDs in the provided encoded set.
+   */
+  public static long[] getEncodedIDs(byte[] bytes, int offset)
+  {
+    int length = getEncodedSize(bytes, offset);
+    byte[] entryIDBytes = new byte[length * 8];
+    System.arraycopy(bytes, offset+4, entryIDBytes, 0, entryIDBytes.length);
+    return JebFormat.entryIDListFromDatabase(entryIDBytes);
+  }
+
+  /**
+   * Searches this set for the specified values and entry ID using the binary
+   * search algorithm.
+   *
+   * @param entryID The entry ID to match or -1 if not matching on entry ID.
+   * @param values The values to match.
+   * @return Index of the entry matching the values and optionally the entry ID
+   * if it is found or a negative index if its not found.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  int binarySearch(long entryID, AttributeValue[] values)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      return -1;
+    }
+
+    int i = 0;
+    for(int j = entryIDs.length - 1; i <= j;)
+    {
+      int k = i + j >> 1;
+      int l = vlvIndex.comparator.compareValuesInSet(this, k, entryID, values);
+      if(l < 0)
+        i = k + 1;
+      else
+      if(l > 0)
+        j = k - 1;
+      else
+        return k;
+    }
+
+    return -(i + 1);
+  }
+
+  /**
+   * Retrieve the size of this set.
+   *
+   * @return The size of this set.
+   */
+  public int size()
+  {
+    if(entryIDs == null)
+    {
+      return 0;
+    }
+
+    return entryIDs.length;
+  }
+
+  /**
+   * Retrieve the entry IDs in this set.
+   *
+   * @return The entry IDs in this set.
+   */
+  public long[] getEntryIDs()
+  {
+    return entryIDs;
+  }
+
+  private static int encodedLengthSize(int length)
+  {
+    if ((length & 0x000000FF) == length)
+    {
+      return 1;
+    }
+    else if ((length & 0x0000FFFF) == length)
+    {
+      return 2;
+    }
+    else if ((length & 0x00FFFFFF) == length)
+    {
+      return 3;
+    }
+    else
+    {
+      return 4;
+    }
+  }
+
+  private byte[] attributeValuesToDatabase(AttributeValue[] values)
+      throws DirectoryException
+  {
+    byte[] valuesBytes = new byte[values.length *
+        (ENCODED_ATTRIBUTE_VALUE_SIZE)];
+    for(int i = 0; i < values.length; i++)
+    {
+      AttributeValue value = values[i];
+      int length;
+      byte[] lengthBytes = new byte[ENCODED_VALUE_LENGTH_SIZE];
+      if(value == null)
+      {
+        length = 0;
+      }
+      else
+      {
+        byte[] valueBytes = value.getNormalizedValueBytes();
+        length = valueBytes.length;
+        if(valueBytes.length > ENCODED_VALUE_SIZE)
+        {
+          System.arraycopy(valueBytes, 0, valuesBytes,
+                           i * ENCODED_ATTRIBUTE_VALUE_SIZE +
+                               ENCODED_VALUE_LENGTH_SIZE,
+                           ENCODED_VALUE_SIZE);
+        }
+        else
+        {
+          System.arraycopy(valueBytes, 0, valuesBytes,
+                           i * ENCODED_ATTRIBUTE_VALUE_SIZE +
+                               ENCODED_VALUE_LENGTH_SIZE,
+                           valueBytes.length);
+        }
+      }
+
+      for (int j = ENCODED_VALUE_LENGTH_SIZE - 1; j >= 0; j--)
+      {
+        lengthBytes[j] = (byte) (length & 0xFF);
+        length >>>= 8;
+      }
+
+      System.arraycopy(lengthBytes, 0, valuesBytes,
+                       i * (ENCODED_ATTRIBUTE_VALUE_SIZE),
+                       lengthBytes.length);
+    }
+    return valuesBytes;
+  }
+
+  /**
+   * Returns the key to use for this set of sort values in the database.
+   *
+   * @return The key as an array of bytes that should be used for this set in
+   * the database or NULL if this set is empty.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public byte[] getKeyBytes()
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      return null;
+    }
+
+    if(keyBytes != null)
+    {
+      return keyBytes;
+    }
+
+    SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
+    int numValues = sortKeys.length;
+    AttributeValue[] values =
+        new AttributeValue[numValues];
+    for (int i = (entryIDs.length - 1) * numValues, j = 0;
+         i < entryIDs.length * numValues;
+         i++, j++)
+    {
+      values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
+                                     new ASN1OctetString(getValue(i)));
+    }
+    keyBytes = vlvIndex.encodeKey(entryIDs[entryIDs.length - 1], values);
+    return keyBytes;
+  }
+
+  /**
+   * Returns the key to use for this set of sort values in the database.
+   *
+   * @return The key as a sort values object that should be used for this set in
+   * the database or NULL if this set is empty or unbounded.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public SortValues getKeySortValues()
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      return null;
+    }
+
+    if(keyBytes != null && keyBytes.length == 0)
+    {
+      return null;
+    }
+
+    EntryID id = new EntryID(entryIDs[entryIDs.length - 1]);
+    SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
+    int numValues = sortKeys.length;
+    AttributeValue[] values =
+        new AttributeValue[numValues];
+    for (int i = (entryIDs.length - 1) * numValues, j = 0;
+         i < entryIDs.length * numValues;
+         i++, j++)
+    {
+      values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
+                                     new ASN1OctetString(getValue(i)));
+    }
+
+    return new SortValues(id, values, vlvIndex.sortOrder);
+  }
+
+  /**
+   * Returns the sort values at the index in this set.
+   *
+   * @param index The index of the sort values to get.
+   * @return The sort values object at the specified index.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   **/
+  public SortValues getSortValues(int index)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(entryIDs == null || entryIDs.length == 0)
+    {
+      return null;
+    }
+
+    EntryID id = new EntryID(entryIDs[index]);
+    SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
+    int numValues = sortKeys.length;
+    AttributeValue[] values =
+        new AttributeValue[numValues];
+    for (int i = index * numValues, j = 0;
+         i < (index + 1) * numValues;
+         i++, j++)
+    {
+      byte[] value = getValue(i);
+
+      if(value != null)
+      {
+        values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
+                                       new ASN1OctetString(value));
+      }
+    }
+
+    return new SortValues(id, values, vlvIndex.sortOrder);
+  }
+
+  /**
+   * Retrieve an attribute value from this values set. The index is the
+   * absolute index. (ie. for a sort on 3 attributes per entry, an vlvIndex of 6
+   * will be the 1st attribute value of the 3rd entry).
+   *
+   * @param index The vlvIndex of the attribute value to retrieve.
+   * @return The byte array representation of the attribute value.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public byte[] getValue(int index)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    // If values bytes is null, we have to get the value by getting the
+    // entry by ID and getting the value.
+    if(valuesBytes != null)
+    {
+      int pos = index * ENCODED_ATTRIBUTE_VALUE_SIZE;
+      int length = 0;
+      byte[] valueBytes;
+      for (int k = 0; k < ENCODED_VALUE_LENGTH_SIZE; k++, pos++)
+      {
+        length <<= 8;
+        length |= (valuesBytes[pos] & 0xFF);
+      }
+
+      if(length == 0)
+      {
+        return null;
+      }
+      // If the value has exceeded the max value size, we have to get the
+      // value by getting the entry by ID.
+      else if(length <= ENCODED_VALUE_SIZE && length > 0)
+      {
+        valueBytes = new byte[length];
+        System.arraycopy(valuesBytes, pos, valueBytes, 0, length);
+
+        return valueBytes;
+      }
+    }
+
+    // Get the entry from id2entry and assign the values from the entry.
+    // Once the values are assigned from the retrieved entry, it will
+    // not be retrieve again from future compares.
+    EntryID id = new EntryID(entryIDs[index /
+        vlvIndex.sortOrder.getSortKeys().length]);
+    AttributeValue[] values = cachedAttributeValues.get(id);
+    if(values == null)
+    {
+      values = vlvIndex.getSortValues(id2entry.get(null, id));
+      cachedAttributeValues.put(id, values);
+    }
+    int offset = index % values.length;
+    return values[offset].getNormalizedValueBytes();
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/State.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/State.java
index fb181b7..fd299b1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/State.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/State.java
@@ -136,6 +136,34 @@
   }
 
   /**
+   * Fetch index state from the database.
+   * @param txn The database transaction or null if none.
+   * @param vlvIndex The index storing the trusted state info.
+   * @return The trusted state of the index in the database.
+   * @throws DatabaseException If an error occurs in the JE database.
+   */
+  public boolean getIndexTrustState(Transaction txn, VLVIndex vlvIndex)
+      throws DatabaseException
+  {
+    String shortName =
+        vlvIndex.getName().replace(entryContainer.getDatabasePrefix(), "");
+    DatabaseEntry key =
+        new DatabaseEntry(StaticUtils.getBytes(shortName));
+    DatabaseEntry data = new DatabaseEntry();
+
+    OperationStatus status;
+    status = read(txn, key, data, LockMode.DEFAULT);
+
+    if (status != OperationStatus.SUCCESS)
+    {
+      return false;
+    }
+
+    byte[] bytes = data.getData();
+    return Arrays.equals(bytes, trueBytes);
+  }
+
+  /**
    * Put index state to database.
    * @param txn The database transaction or null if none.
    * @param index The index storing the trusted state info.
@@ -147,10 +175,10 @@
                                     boolean trusted)
        throws DatabaseException
   {
-    String sortName =
+    String shortName =
         index.getName().replace(entryContainer.getDatabasePrefix(), "");
     DatabaseEntry key =
-        new DatabaseEntry(StaticUtils.getBytes(sortName));
+        new DatabaseEntry(StaticUtils.getBytes(shortName));
     DatabaseEntry data = new DatabaseEntry();
 
     if(trusted)
@@ -167,5 +195,35 @@
     return true;
   }
 
-  // TODO: Make sure to update the VLV state access methods to use shortname.
+  /**
+   * Put VLV index state to database.
+   * @param txn The database transaction or null if none.
+   * @param vlvIndex The VLV index storing the trusted state info.
+   * @param trusted The state value to put into the database.
+   * @return true if the entry was written, false if it was not.
+   * @throws DatabaseException If an error occurs in the JE database.
+   */
+  public boolean putIndexTrustState(Transaction txn, VLVIndex vlvIndex,
+                                    boolean trusted)
+       throws DatabaseException
+  {
+    String shortName =
+        vlvIndex.getName().replace(entryContainer.getDatabasePrefix(), "");
+    DatabaseEntry key =
+        new DatabaseEntry(StaticUtils.getBytes(shortName));
+    DatabaseEntry data = new DatabaseEntry();
+
+    if(trusted)
+      data.setData(trueBytes);
+    else
+      data.setData(falseBytes);
+
+    OperationStatus status;
+    status = put(txn, key, data);
+    if (status != OperationStatus.SUCCESS)
+    {
+      return false;
+    }
+    return true;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndex.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndex.java
new file mode 100644
index 0000000..a6c983d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndex.java
@@ -0,0 +1,1451 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import com.sleepycat.je.*;
+import org.opends.server.loggers.debug.DebugTracer;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.types.*;
+import org.opends.server.admin.std.server.VLVJEIndexCfg;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SearchOperation;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
+import static org.opends.server.messages.JebMessages.
+    MSGID_ENTRYIDSORTER_NEGATIVE_START_POS;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER;
+
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import org.opends.server.util.StaticUtils;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import org.opends.server.api.OrderingMatchingRule;
+import org.opends.server.config.ConfigException;
+import org.opends.server.protocols.asn1.ASN1Element;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.controls.VLVRequestControl;
+import org.opends.server.controls.VLVResponseControl;
+import org.opends.server.controls.ServerSideSortRequestControl;
+
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a VLV index. Each database record is a sorted list
+ * of entry IDs followed by sets of attribute values used to sort the entries.
+ * The entire set of entry IDs are broken up into sorted subsets to decrease
+ * the number of database retrivals needed for a range lookup. The records are
+ * keyed by the last entry's first sort attribute value. The list of entries
+ * in a particular database record maintains the property where the first sort
+ * attribute value is bigger then the previous key but smaller or equal
+ * to its own key.
+ */
+public class VLVIndex extends DatabaseContainer
+    implements ConfigurationChangeListener<VLVJEIndexCfg>
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * The comparator for vlvIndex keys.
+   */
+  public VLVKeyComparator comparator;
+
+  /**
+   * The limit on the number of entry IDs that may be indexed by one key.
+   */
+  private int sortedSetCapacity = 4000;
+
+  /**
+   * The cached count of entries in this index.
+   */
+  private AtomicInteger count;
+
+  private State state;
+
+  /**
+   * A flag to indicate if this vlvIndex should be trusted to be consistent
+   * with the entries database.
+   */
+  private boolean trusted = false;
+
+  /**
+   * A flag to indicate if a rebuild process is running on this vlvIndex.
+   */
+  private boolean rebuildRunning = false;
+
+  /**
+   * The VLV vlvIndex configuration.
+   */
+  private VLVJEIndexCfg config;
+
+  private ID2Entry id2entry;
+
+  private DN baseDN;
+
+  private SearchFilter filter;
+
+  private SearchScope scope;
+
+  /**
+   * The SortOrder in use by this VLV index to sort the entries.
+   */
+  public SortOrder sortOrder;
+
+
+  /**
+   * Create a new VLV vlvIndex object.
+   *
+   * @param config           The VLV index config object to use for this VLV
+   *                         index.
+   * @param state            The state database to persist vlvIndex state info.
+   * @param env              The JE Environemnt
+   * @param entryContainer   The database entryContainer holding this vlvIndex.
+   * @throws com.sleepycat.je.DatabaseException
+   *          If an error occurs in the JE database.
+   * @throws ConfigException if a error occurs while reading the VLV index
+   * configuration
+   */
+  public VLVIndex(VLVJEIndexCfg config, State state, Environment env,
+                  EntryContainer entryContainer)
+      throws DatabaseException, ConfigException
+  {
+    super(entryContainer.getDatabasePrefix()+"_vlv."+config.getVLVIndexName(),
+          env, entryContainer);
+
+    this.config = config;
+    this.baseDN = config.getVLVIndexBaseDN();
+    this.scope = SearchScope.valueOf(config.getVLVIndexScope().name());
+    this.sortedSetCapacity = config.getVLVIndexSortedSetCapacity();
+    this.id2entry = entryContainer.getID2Entry();
+
+    try
+    {
+      this.filter =
+          SearchFilter.createFilterFromString(config.getVLVIndexFilter());
+    }
+    catch(Exception e)
+    {
+      int msgID = MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER;
+      String msg = getMessage(msgID, config.getVLVIndexFilter(), name,
+                              stackTraceToSingleLineString(e));
+      throw new ConfigException(msgID, msg);
+    }
+
+    String[] sortAttrs = config.getVLVIndexSortOrder().split(" ");
+    SortKey[] sortKeys = new SortKey[sortAttrs.length];
+    OrderingMatchingRule[] orderingRules =
+        new OrderingMatchingRule[sortAttrs.length];
+    boolean[] ascending = new boolean[sortAttrs.length];
+    for(int i = 0; i < sortAttrs.length; i++)
+    {
+      try
+      {
+        if(sortAttrs[i].startsWith("-"))
+        {
+          ascending[i] = false;
+          sortAttrs[i] = sortAttrs[i].substring(1);
+        }
+        else
+        {
+          ascending[i] = true;
+          if(sortAttrs[i].startsWith("+"))
+          {
+            sortAttrs[i] = sortAttrs[i].substring(1);
+          }
+        }
+      }
+      catch(Exception e)
+      {
+        int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+        String msg = getMessage(msgID, sortKeys[i], name);
+        throw new ConfigException(msgID, msg);
+      }
+
+      AttributeType attrType =
+          DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase());
+      if(attrType == null)
+      {
+        int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+        String msg = getMessage(msgID, sortKeys[i], name);
+        throw new ConfigException(msgID, msg);
+      }
+      sortKeys[i] = new SortKey(attrType, ascending[i]);
+      orderingRules[i] = attrType.getOrderingMatchingRule();
+    }
+
+    this.sortOrder = new SortOrder(sortKeys);
+    this.comparator = new VLVKeyComparator(orderingRules, ascending);
+
+    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
+
+    if(env.getConfig().getReadOnly())
+    {
+      dbNodupsConfig.setReadOnly(true);
+      dbNodupsConfig.setAllowCreate(false);
+      dbNodupsConfig.setTransactional(false);
+    }
+    else if(!env.getConfig().getTransactional())
+    {
+      dbNodupsConfig.setAllowCreate(true);
+      dbNodupsConfig.setTransactional(false);
+      dbNodupsConfig.setDeferredWrite(true);
+    }
+    else
+    {
+      dbNodupsConfig.setAllowCreate(true);
+      dbNodupsConfig.setTransactional(true);
+    }
+
+    this.dbConfig = dbNodupsConfig;
+    this.dbConfig.setOverrideBtreeComparator(true);
+    this.dbConfig.setBtreeComparator(this.comparator);
+
+    this.state = state;
+
+    this.trusted = state.getIndexTrustState(null, this);
+    if(!trusted && entryContainer.getEntryCount() <= 0)
+    {
+      // If there are no entries in the entry container then there
+      // is no reason why this vlvIndex can't be upgraded to trusted.
+      setTrusted(null, true);
+    }
+
+    // Issue warning if this vlvIndex is not trusted
+    if(!trusted)
+    {
+      int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
+      ErrorLogger.logError(ErrorLogCategory.BACKEND,
+                           ErrorLogSeverity.NOTICE, msgID, name);
+    }
+
+    this.count = new AtomicInteger(0);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void open() throws DatabaseException
+  {
+    super.open();
+
+    DatabaseEntry key = new DatabaseEntry();
+    OperationStatus status;
+    LockMode lockMode = LockMode.RMW;
+    DatabaseEntry data = new DatabaseEntry();
+
+    Cursor cursor = openCursor(null, CursorConfig.READ_COMMITTED);
+
+    try
+    {
+      status = cursor.getFirst(key, data,lockMode);
+      while(status == OperationStatus.SUCCESS)
+      {
+        count.getAndAdd(SortValuesSet.getEncodedSize(data.getData(), 0));
+        status = cursor.getNext(key, data, lockMode);
+      }
+    }
+    finally
+    {
+      cursor.close();
+    }
+  }
+
+  /**
+   * Update the vlvIndex for a new entry.
+   *
+   * @param txn A database transaction, or null if none is required.
+   * @param entryID     The entry ID.
+   * @param entry       The entry to be indexed.
+   * @return True if the entry ID for the entry are added. False if
+   *         the entry ID already exists.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws org.opends.server.types.DirectoryException If a Directory Server
+   * error occurs.
+   * @throws JebException If an error occurs in the JE backend.
+   */
+  public boolean addEntry(Transaction txn, EntryID entryID, Entry entry)
+      throws DatabaseException, DirectoryException, JebException
+  {
+    DN entryDN = entry.getDN();
+    if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry))
+    {
+      return insertValues(txn, entryID.longValue(), entry);
+    }
+    return false;
+  }
+
+  /**
+   * Update the vlvIndex for a deleted entry.
+   *
+   * @param txn         The database transaction to be used for the deletions
+   * @param entryID     The entry ID
+   * @param entry       The contents of the deleted entry.
+   * @return True if the entry was successfully removed from this VLV index
+   * or False otherwise.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws JebException If an error occurs in the JE backend.
+   */
+  public boolean removeEntry(Transaction txn, EntryID entryID, Entry entry)
+      throws DatabaseException, DirectoryException, JebException
+  {
+    DN entryDN = entry.getDN();
+    if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry))
+    {
+      return removeValues(txn, entryID.longValue(), entry);
+    }
+    return false;
+  }
+
+  /**
+   * Update the vlvIndex to reflect a sequence of modifications in a Modify
+   * operation.
+   *
+   * @param txn The JE transaction to use for database updates.
+   * @param entryID The ID of the entry that was modified.
+   * @param oldEntry The entry before the modifications were applied.
+   * @param newEntry The entry after the modifications were applied.
+   * @param mods The sequence of modifications in the Modify operation.
+   * @return True if the modification was successfully processed or False
+   * otherwise.
+   * @throws JebException If an error occurs during an operation on a
+   * JE database.
+   * @throws DatabaseException If an error occurs during an operation on a
+   * JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  public boolean modifyEntry(Transaction txn,
+                          EntryID entryID,
+                          Entry oldEntry,
+                          Entry newEntry,
+                          List<Modification> mods)
+       throws DatabaseException, DirectoryException, JebException
+  {
+    DN oldEntryDN = oldEntry.getDN();
+    DN newEntryDN = newEntry.getDN();
+    if(oldEntryDN.matchesBaseAndScope(baseDN, scope) &&
+        filter.matchesEntry(oldEntry))
+    {
+      if(newEntryDN.matchesBaseAndScope(baseDN, scope) &&
+          filter.matchesEntry(newEntry))
+      {
+        // The entry should still be indexed. See if any sorted attributes are
+        // changed.
+        boolean sortAttributeModified = false;
+        SortKey[] sortKeys = sortOrder.getSortKeys();
+        for(SortKey sortKey : sortKeys)
+        {
+          for(Modification mod : mods)
+          {
+            if(mod.getAttribute().getAttributeType().
+                equals(sortKey.getAttributeType()))
+            {
+              sortAttributeModified = true;
+              break;
+            }
+          }
+          if(sortAttributeModified)
+          {
+            break;
+          }
+        }
+        if(sortAttributeModified)
+        {
+          boolean success;
+          // Sorted attributes have changed. Reindex the entry;
+          success = removeValues(txn, entryID.longValue(), oldEntry);
+          success &= insertValues(txn, entryID.longValue(), newEntry);
+          return success;
+        }
+      }
+      else
+      {
+        // The modifications caused the new entry to be unindexed. Remove from
+        // vlvIndex.
+        return removeValues(txn, entryID.longValue(), oldEntry);
+      }
+    }
+    else
+    {
+      if(newEntryDN.matchesBaseAndScope(baseDN, scope) &&
+          filter.matchesEntry(newEntry))
+      {
+        // The modifications caused the new entry to be indexed. Add to
+        // vlvIndex.
+        return insertValues(txn, entryID.longValue(), newEntry);
+      }
+    }
+
+    // The modifications does not affect this vlvIndex
+    return true;
+  }
+
+  /**
+   * Put a sort values set in this VLV index.
+   *
+   * @param txn The transaction to use when retriving the set or NULL if it is
+   *            not required.
+   * @param sortValuesSet The SortValuesSet to put.
+   * @return True if the sortValuesSet was put successfully or False otherwise.
+   * @throws JebException If an error occurs during an operation on a
+   * JE database.
+   * @throws DatabaseException If an error occurs during an operation on a
+   * JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  public boolean putSortValuesSet(Transaction txn, SortValuesSet sortValuesSet)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    DatabaseEntry key = new DatabaseEntry();
+    DatabaseEntry data = new DatabaseEntry();
+
+    byte[] after = sortValuesSet.toDatabase();
+    key.setData(sortValuesSet.getKeyBytes());
+    data.setData(after);
+    return put(txn, key, data) == OperationStatus.SUCCESS;
+  }
+
+  /**
+   * Get a sorted values set that should contain the entry with the given
+   * information.
+   *
+   * @param txn The transaction to use when retriving the set or NULL if it is
+   *            not required.
+   * @param entryID The entry ID to use.
+   * @param values The values to use.
+   * @return The SortValuesSet that should contain the entry with the given
+   *         information.
+   * @throws DatabaseException If an error occurs during an operation on a
+   * JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  public SortValuesSet getSortValuesSet(Transaction txn, long entryID,
+                                        AttributeValue[] values)
+      throws DatabaseException, DirectoryException
+  {
+    SortValuesSet sortValuesSet = null;
+    DatabaseEntry key = new DatabaseEntry();
+    OperationStatus status;
+    LockMode lockMode = LockMode.DEFAULT;
+    DatabaseEntry data = new DatabaseEntry();
+
+    Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+    try
+    {
+      key.setData(encodeKey(entryID, values));
+      status = cursor.getSearchKeyRange(key, data,lockMode);
+
+      if(status != OperationStatus.SUCCESS)
+      {
+        // There are no records in the database
+        if(debugEnabled())
+        {
+          TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " +
+              "Creating unbound set.", config.getVLVIndexName());
+        }
+        sortValuesSet = new SortValuesSet(this, id2entry);
+      }
+      else
+      {
+        if(debugEnabled())
+        {
+          StringBuilder searchKeyHex = new StringBuilder();
+          StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4);
+          StringBuilder foundKeyHex = new StringBuilder();
+          StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4);
+          TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " +
+              "%s\nSearch Key:%s\nFound Key:%s\n",
+                              config.getVLVIndexName(),
+                              searchKeyHex,
+                              foundKeyHex);
+        }
+        sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
+                                          this, id2entry);
+      }
+    }
+    finally
+    {
+      cursor.close();
+    }
+
+    return sortValuesSet;
+  }
+
+  /**
+   * Search for entries matching the entry ID and attribute values and
+   * return its entry ID.
+   *
+   * @param txn The JE transaction to use for database updates.
+   * @param entryID The entry ID to search for.
+   * @param values The values to search for.
+   * @return The index of the entry ID matching the values or -1 if its not
+   * found.
+   * @throws DatabaseException If an error occurs during an operation on a
+   * JE database.
+   * @throws JebException If an error occurs during an operation on a
+   * JE database.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  public boolean containsValues(Transaction txn, long entryID,
+                             AttributeValue[] values)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    SortValuesSet valuesSet = getSortValuesSet(txn, entryID, values);
+    int pos = valuesSet.binarySearch(entryID, values);
+    if(pos < 0)
+    {
+      return false;
+    }
+    return true;
+  }
+
+  private boolean insertValues(Transaction txn, long entryID, Entry entry)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    SortValuesSet sortValuesSet;
+    AttributeValue[] values = getSortValues(entry);
+    DatabaseEntry key = new DatabaseEntry();
+    OperationStatus status;
+    LockMode lockMode = LockMode.RMW;
+    DatabaseEntry data = new DatabaseEntry();
+    boolean success = true;
+
+    Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+    try
+    {
+      key.setData(encodeKey(entryID, values));
+      status = cursor.getSearchKeyRange(key, data,lockMode);
+    }
+    finally
+    {
+      cursor.close();
+    }
+
+    if(status != OperationStatus.SUCCESS)
+    {
+      // There are no records in the database
+      if(debugEnabled())
+      {
+        TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " +
+            "Creating unbound set.", config.getVLVIndexName());
+      }
+      sortValuesSet = new SortValuesSet(this, id2entry);
+      key.setData(new byte[0]);
+    }
+    else
+    {
+      if(debugEnabled())
+      {
+        StringBuilder searchKeyHex = new StringBuilder();
+        StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4);
+        StringBuilder foundKeyHex = new StringBuilder();
+        StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4);
+        TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " +
+            "%s\nSearch Key:%s\nFound Key:%s\n",
+                            config.getVLVIndexName(),
+                            searchKeyHex,
+                            foundKeyHex);
+      }
+      sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
+                                        this, id2entry);
+    }
+
+
+
+
+    success = sortValuesSet.add(entryID, values);
+
+    int newSize = sortValuesSet.size();
+    if(newSize >= sortedSetCapacity)
+    {
+      SortValuesSet splitSortValuesSet = sortValuesSet.split(newSize / 2);
+      byte[] splitAfter = splitSortValuesSet.toDatabase();
+      key.setData(splitSortValuesSet.getKeyBytes());
+      data.setData(splitAfter);
+      put(txn, key, data);
+      byte[] after = sortValuesSet.toDatabase();
+      key.setData(sortValuesSet.getKeyBytes());
+      data.setData(after);
+      put(txn, key, data);
+
+      if(debugEnabled())
+      {
+        TRACER.debugInfo("SortValuesSet with key %s has reached" +
+            " the entry size of %d. Spliting into two sets with " +
+            " keys %s and %s.", splitSortValuesSet.getKeySortValues(),
+                                newSize, sortValuesSet.getKeySortValues(),
+                                splitSortValuesSet.getKeySortValues());
+      }
+    }
+    else
+    {
+      byte[] after = sortValuesSet.toDatabase();
+      data.setData(after);
+      put(txn, key, data);
+    }
+
+    if(success)
+    {
+      count.getAndIncrement();
+    }
+
+    return success;
+  }
+
+  private boolean removeValues(Transaction txn, long entryID, Entry entry)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    SortValuesSet sortValuesSet;
+    AttributeValue[] values = getSortValues(entry);
+    DatabaseEntry key = new DatabaseEntry();
+    OperationStatus status;
+    LockMode lockMode = LockMode.RMW;
+    DatabaseEntry data = new DatabaseEntry();
+
+    Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+    try
+    {
+      key.setData(encodeKey(entryID, values));
+      status = cursor.getSearchKeyRange(key, data,lockMode);
+    }
+    finally
+    {
+      cursor.close();
+    }
+
+    if(status == OperationStatus.SUCCESS)
+    {
+      if(debugEnabled())
+      {
+        StringBuilder searchKeyHex = new StringBuilder();
+        StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4);
+        StringBuilder foundKeyHex = new StringBuilder();
+        StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4);
+        TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " +
+            "%s\nSearch Key:%s\nFound Key:%s\n",
+                            config.getVLVIndexName(),
+                            searchKeyHex,
+                            foundKeyHex);
+      }
+      sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
+                                        this, id2entry);
+      boolean success = sortValuesSet.remove(entryID, values);
+      byte[] after = sortValuesSet.toDatabase();
+      data.setData(after);
+      put(txn, key, data);
+
+      if(success)
+      {
+        count.getAndDecrement();
+      }
+
+      return success;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  /**
+   * Evaluate a search with sort control using this VLV index.
+   *
+   * @param txn The transaction to used when reading the index or NULL if it is
+   *            not required.
+   * @param searchOperation The search operation to evaluate.
+   * @param sortControl The sort request control to evaluate.
+   * @param vlvRequest The VLV request control to evaluate or NULL if VLV is not
+   *                   requested.
+   * @param debugBuilder If not null, a diagnostic string will be written
+   *                     which will help determine how this index contributed
+   *                     to this search.
+   * @return The sorted EntryIDSet containing the entry IDs that match the
+   *         search criteria.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws JebException If an error occurs in the JE database.
+   */
+  public EntryIDSet evaluate(Transaction txn,
+                             SearchOperation searchOperation,
+                             ServerSideSortRequestControl sortControl,
+                             VLVRequestControl vlvRequest,
+                             StringBuilder debugBuilder)
+      throws DirectoryException, DatabaseException, JebException
+  {
+    if(!trusted || rebuildRunning)
+    {
+      return null;
+    }
+    if(!searchOperation.getBaseDN().equals(baseDN))
+    {
+      return null;
+    }
+    if(!searchOperation.getScope().equals(scope))
+    {
+      return null;
+    }
+    if(!searchOperation.getFilter().equals(filter))
+    {
+      return null;
+    }
+    if(!sortControl.getSortOrder().equals(this.sortOrder))
+    {
+      return null;
+    }
+
+    if (debugBuilder != null)
+    {
+      debugBuilder.append("vlv=");
+      debugBuilder.append("[INDEX:");
+      debugBuilder.append(name.replace(entryContainer.getDatabasePrefix() + "_",
+                                       ""));
+      debugBuilder.append("]");
+    }
+
+    long[] selectedIDs = new long[0];
+    if(vlvRequest != null)
+    {
+      int currentCount = count.get();
+      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, currentCount,
+                                     LDAPResultCode.OFFSET_RANGE_ERROR));
+
+          int    msgID   = MSGID_ENTRYIDSORTER_NEGATIVE_START_POS;
+          String message = getMessage(msgID);
+          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 >= currentCount)
+        {
+          // 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.
+          // 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 = currentCount + 1;
+          listOffset   = currentCount;
+          startPos     = listOffset - beforeCount;
+          afterCount   = 0;
+        }
+
+        int count = 1 + beforeCount + afterCount;
+        selectedIDs = new long[count];
+
+        DatabaseEntry key = new DatabaseEntry();
+        OperationStatus status;
+        LockMode lockMode = LockMode.DEFAULT;
+        DatabaseEntry data = new DatabaseEntry();
+
+        Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+        try
+        {
+          //Locate the set that contains the target entry.
+          int cursorCount = 0;
+          int selectedPos = 0;
+          status = cursor.getFirst(key, data,lockMode);
+          while(status == OperationStatus.SUCCESS)
+          {
+            if(debugEnabled())
+            {
+              StringBuilder searchKeyHex = new StringBuilder();
+              StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(),
+                                                  4);
+              StringBuilder foundKeyHex = new StringBuilder();
+              StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(),
+                                                  4);
+              TRACER.debugVerbose("Retrieved a sort values set in VLV " +
+                  "vlvIndex %s\nSearch Key:%s\nFound Key:%s\n",
+                                  config.getVLVIndexName(),
+                                  searchKeyHex,
+                                  foundKeyHex);
+            }
+            long[] IDs = SortValuesSet.getEncodedIDs(data.getData(), 0);
+            for(int i = startPos + selectedPos - cursorCount;
+                i < IDs.length && selectedPos < count;
+                i++, selectedPos++)
+            {
+              selectedIDs[selectedPos] = IDs[i];
+            }
+            cursorCount += IDs.length;
+            status = cursor.getNext(key, data,lockMode);
+          }
+
+          if (selectedPos < 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[selectedPos];
+            System.arraycopy(selectedIDs, 0, newIDArray, 0, selectedPos);
+            selectedIDs = newIDArray;
+          }
+
+          searchOperation.addResponseControl(
+              new VLVResponseControl(targetOffset, currentCount,
+                                     LDAPResultCode.SUCCESS));
+
+          if(debugBuilder != null)
+          {
+            debugBuilder.append("[COUNT:");
+            debugBuilder.append(cursorCount);
+            debugBuilder.append("]");
+          }
+        }
+        finally
+        {
+          cursor.close();
+        }
+      }
+      else
+      {
+        int targetOffset = 0;
+        int includedBeforeCount = 0;
+        int includedAfterCount  = 0;
+        LinkedList<EntryID> idList = new LinkedList<EntryID>();
+        DatabaseEntry key = new DatabaseEntry();
+        OperationStatus status;
+        LockMode lockMode = LockMode.DEFAULT;
+        DatabaseEntry data = new DatabaseEntry();
+
+        Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+        try
+        {
+          byte[] vBytes = vlvRequest.getGreaterThanOrEqualAssertion().value();
+          byte[] vLength = ASN1Element.encodeLength(vBytes.length);
+          byte[] keyBytes = new byte[vBytes.length + vLength.length];
+          System.arraycopy(vLength, 0, keyBytes, 0, vLength.length);
+          System.arraycopy(vBytes, 0, keyBytes, vLength.length, vBytes.length);
+
+          key.setData(keyBytes);
+          status = cursor.getSearchKeyRange(key, data, lockMode);
+          if(status == OperationStatus.SUCCESS)
+          {
+            if(debugEnabled())
+            {
+              StringBuilder searchKeyHex = new StringBuilder();
+              StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(),
+                                                  4);
+              StringBuilder foundKeyHex = new StringBuilder();
+              StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(),
+                                                  4);
+              TRACER.debugVerbose("Retrieved a sort values set in VLV " +
+                  "vlvIndex %s\nSearch Key:%s\nFound Key:%s\n",
+                                  config.getVLVIndexName(),
+                                  searchKeyHex,
+                                  foundKeyHex);
+            }
+            SortValuesSet sortValuesSet =
+                new SortValuesSet(key.getData(), data.getData(), this,
+                                  id2entry);
+            AttributeValue[] assertionValue = new AttributeValue[1];
+            assertionValue[0] =
+                new AttributeValue(
+                    sortOrder.getSortKeys()[0].getAttributeType(),
+                    vlvRequest.getGreaterThanOrEqualAssertion());
+
+            int adjustedTargetOffset =
+                sortValuesSet.binarySearch(-1, assertionValue);
+            if(adjustedTargetOffset < 0)
+            {
+              // For a negative return value r, the vlvIndex -(r+1) gives the
+              // array index of the ID that is greater then the assertion value.
+              adjustedTargetOffset = -(adjustedTargetOffset+1);
+            }
+
+            targetOffset = adjustedTargetOffset;
+
+            // Iterate through all the sort values sets before this one to find
+            // the target offset in the index.
+            int lastOffset = adjustedTargetOffset - 1;
+            long[] lastIDs = sortValuesSet.getEntryIDs();
+            while(true)
+            {
+              for(int i = lastOffset;
+                  i >= 0 && includedBeforeCount < beforeCount; i--)
+              {
+                idList.addFirst(new EntryID(lastIDs[i]));
+                includedBeforeCount++;
+              }
+
+              status = cursor.getPrev(key, data, lockMode);
+
+              if(status != OperationStatus.SUCCESS)
+              {
+                break;
+              }
+
+              if(includedBeforeCount < beforeCount)
+              {
+                lastIDs =
+                    SortValuesSet.getEncodedIDs(data.getData(), 0);
+                lastOffset = lastIDs.length - 1;
+                targetOffset += lastIDs.length;
+              }
+              else
+              {
+                targetOffset += SortValuesSet.getEncodedSize(data.getData(), 0);
+              }
+            }
+
+
+            // Set the cursor back to the position of the target entry set
+            key.setData(sortValuesSet.getKeyBytes());
+            cursor.getSearchKey(key, data, lockMode);
+
+            // Add the target and after count entries if the target was found.
+            lastOffset = adjustedTargetOffset;
+            lastIDs = sortValuesSet.getEntryIDs();
+            int afterIDCount = 0;
+            while(true)
+            {
+              for(int i = lastOffset;
+                  i < lastIDs.length && includedAfterCount < afterCount + 1;
+                  i++)
+              {
+                idList.addLast(new EntryID(lastIDs[i]));
+                includedAfterCount++;
+              }
+
+              if(includedAfterCount >= afterCount + 1)
+              {
+                break;
+              }
+
+              status = cursor.getNext(key, data, lockMode);
+
+              if(status != OperationStatus.SUCCESS)
+              {
+                break;
+              }
+
+              lastIDs =
+                  SortValuesSet.getEncodedIDs(data.getData(), 0);
+              lastOffset = 0;
+              afterIDCount += lastIDs.length;
+            }
+
+            selectedIDs = new long[idList.size()];
+            Iterator<EntryID> idIterator = idList.iterator();
+            for (int i=0; i < selectedIDs.length; i++)
+            {
+              selectedIDs[i] = idIterator.next().longValue();
+            }
+
+            searchOperation.addResponseControl(
+                new VLVResponseControl(targetOffset + 1, currentCount,
+                                       LDAPResultCode.SUCCESS));
+
+            if(debugBuilder != null)
+            {
+              debugBuilder.append("[COUNT:");
+              debugBuilder.append(targetOffset + afterIDCount + 1);
+              debugBuilder.append("]");
+            }
+          }
+        }
+        finally
+        {
+          cursor.close();
+        }
+      }
+    }
+    else
+    {
+      LinkedList<long[]> idSets = new LinkedList<long[]>();
+      int currentCount = 0;
+      DatabaseEntry key = new DatabaseEntry();
+      OperationStatus status;
+      LockMode lockMode = LockMode.RMW;
+      DatabaseEntry data = new DatabaseEntry();
+
+      Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
+
+      try
+      {
+        status = cursor.getFirst(key, data, lockMode);
+        while(status == OperationStatus.SUCCESS)
+        {
+          if(debugEnabled())
+          {
+            StringBuilder searchKeyHex = new StringBuilder();
+            StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4);
+            StringBuilder foundKeyHex = new StringBuilder();
+            StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4);
+            TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " +
+                "%s\nSearch Key:%s\nFound Key:%s\n",
+                                config.getVLVIndexName(),
+                                searchKeyHex,
+                                foundKeyHex);
+          }
+          long[] ids = SortValuesSet.getEncodedIDs(data.getData(), 0);
+          idSets.add(ids);
+          currentCount += ids.length;
+          status = cursor.getNext(key, data, lockMode);
+        }
+      }
+      finally
+      {
+        cursor.close();
+      }
+
+      selectedIDs = new long[currentCount];
+      int pos = 0;
+      for(long[] id : idSets)
+      {
+        System.arraycopy(id, 0, selectedIDs, pos, id.length);
+        pos += id.length;
+      }
+
+      if(debugBuilder != null)
+      {
+        debugBuilder.append("[COUNT:");
+        debugBuilder.append(currentCount);
+        debugBuilder.append("]");
+      }
+    }
+    return new EntryIDSet(selectedIDs, 0, selectedIDs.length);
+  }
+
+    /**
+   * Set the vlvIndex trust state.
+   * @param txn A database transaction, or null if none is required.
+   * @param trusted True if this vlvIndex should be trusted or false
+   *                otherwise.
+   * @throws DatabaseException If an error occurs in the JE database.
+   */
+  public synchronized void setTrusted(Transaction txn, boolean trusted)
+      throws DatabaseException
+  {
+    this.trusted = trusted;
+    state.putIndexTrustState(txn, this, trusted);
+  }
+
+  /**
+   * Set the rebuild status of this vlvIndex.
+   * @param rebuildRunning True if a rebuild process on this vlvIndex
+   *                       is running or False otherwise.
+   */
+  public synchronized void setRebuildStatus(boolean rebuildRunning)
+  {
+    this.rebuildRunning = rebuildRunning;
+  }
+
+  /**
+   * Gets the values to sort on from the entry.
+   *
+   * @param entry The entry to get the values from.
+   * @return The attribute values to sort on.
+   */
+  AttributeValue[] getSortValues(Entry entry)
+  {
+    SortKey[] sortKeys = sortOrder.getSortKeys();
+    AttributeValue[] 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;
+      }
+    }
+    return values;
+  }
+
+  /**
+   * Encode a VLV database key with the given information.
+   *
+   * @param entryID The entry ID to encode.
+   * @param values The values to encode.
+   * @return The encoded bytes.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  byte[] encodeKey(long entryID, AttributeValue[] values)
+      throws DirectoryException
+  {
+    int totalValueBytes = 0;
+    LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
+    for (AttributeValue v : values)
+    {
+      byte[] vBytes;
+      if(v == null)
+      {
+        vBytes = new byte[0];
+      }
+      else
+      {
+        vBytes = v.getNormalizedValueBytes();
+      }
+      byte[] vLength = ASN1Element.encodeLength(vBytes.length);
+      valueBytes.add(vLength);
+      valueBytes.add(vBytes);
+      totalValueBytes += vLength.length + vBytes.length;
+    }
+
+    byte[] entryIDBytes =
+        JebFormat.entryIDToDatabase(entryID);
+    byte[] attrBytes = new byte[entryIDBytes.length + totalValueBytes];
+
+    int pos = 0;
+    for (byte[] b : valueBytes)
+    {
+      System.arraycopy(b, 0, attrBytes, pos, b.length);
+      pos += b.length;
+    }
+
+    System.arraycopy(entryIDBytes, 0, attrBytes, pos, entryIDBytes.length);
+
+    return attrBytes;
+  }
+
+  /**
+   * Get the sorted set capacity configured for this VLV index.
+   *
+   * @return The sorted set capacity.
+   */
+  public int getSortedSetCapacity()
+  {
+    return sortedSetCapacity;
+  }
+
+  /**
+   * Indicates if the given entry should belong in this VLV index.
+   *
+   * @param entry The entry to check.
+   * @return True if the given entry should belong in this VLV index or False
+   *         otherwise.
+   * @throws DirectoryException If a Directory Server error occurs.
+   */
+  public boolean shouldInclude(Entry entry) throws DirectoryException
+  {
+    DN entryDN = entry.getDN();
+    if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry))
+    {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public synchronized boolean isConfigurationChangeAcceptable(
+      VLVJEIndexCfg cfg,
+      List<String> unacceptableReasons)
+  {
+    try
+    {
+      this.filter =
+          SearchFilter.createFilterFromString(config.getVLVIndexFilter());
+    }
+    catch(Exception e)
+    {
+      int msgID = MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER;
+      String msg = getMessage(msgID, config.getVLVIndexFilter(), name,
+                              stackTraceToSingleLineString(e));
+      unacceptableReasons.add(msg);
+      return false;
+    }
+
+    String[] sortAttrs = config.getVLVIndexSortOrder().split(" ");
+    SortKey[] sortKeys = new SortKey[sortAttrs.length];
+    OrderingMatchingRule[] orderingRules =
+        new OrderingMatchingRule[sortAttrs.length];
+    boolean[] ascending = new boolean[sortAttrs.length];
+    for(int i = 0; i < sortAttrs.length; i++)
+    {
+      try
+      {
+        if(sortAttrs[i].startsWith("-"))
+        {
+          ascending[i] = false;
+          sortAttrs[i] = sortAttrs[i].substring(1);
+        }
+        else
+        {
+          ascending[i] = true;
+          if(sortAttrs[i].startsWith("+"))
+          {
+            sortAttrs[i] = sortAttrs[i].substring(1);
+          }
+        }
+      }
+      catch(Exception e)
+      {
+        int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+        String msg = getMessage(msgID, sortKeys[i], name);
+        unacceptableReasons.add(msg);
+        return false;
+      }
+
+      AttributeType attrType =
+          DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase());
+      if(attrType == null)
+      {
+        int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+        String msg = getMessage(msgID, sortKeys[i], name);
+        unacceptableReasons.add(msg);
+        return false;
+      }
+      sortKeys[i] = new SortKey(attrType, ascending[i]);
+      orderingRules[i] = attrType.getOrderingMatchingRule();
+    }
+
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public synchronized ConfigChangeResult applyConfigurationChange(
+      VLVJEIndexCfg cfg)
+  {
+    ResultCode resultCode = ResultCode.SUCCESS;
+    boolean adminActionRequired = false;
+    ArrayList<String> messages = new ArrayList<String>();
+
+    // Update base DN only if changed..
+    if(!config.getVLVIndexBaseDN().equals(cfg.getVLVIndexBaseDN()))
+    {
+      this.baseDN = cfg.getVLVIndexBaseDN();
+      adminActionRequired = true;
+    }
+
+    // Update scope only if changed.
+    if(!config.getVLVIndexScope().equals(cfg.getVLVIndexScope()))
+    {
+      this.scope = SearchScope.valueOf(cfg.getVLVIndexScope().name());
+      adminActionRequired = true;
+    }
+
+    // Update sort set capacity only if changed.
+    if(config.getVLVIndexSortedSetCapacity() !=
+        cfg.getVLVIndexSortedSetCapacity())
+    {
+      this.sortedSetCapacity = cfg.getVLVIndexSortedSetCapacity();
+
+      // Require admin action only if the new capacity is larger. Otherwise,
+      // we will lazyly update the sorted sets.
+      if(config.getVLVIndexSortedSetCapacity() <
+          cfg.getVLVIndexSortedSetCapacity())
+      {
+        adminActionRequired = true;
+      }
+    }
+
+    // Update the filter only if changed.
+    if(!config.getVLVIndexFilter().equals(cfg.getVLVIndexFilter()))
+    {
+      try
+      {
+        this.filter =
+            SearchFilter.createFilterFromString(cfg.getVLVIndexFilter());
+        adminActionRequired = true;
+      }
+      catch(Exception e)
+      {
+        int msgID = MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER;
+        String msg = getMessage(msgID, config.getVLVIndexFilter(), name,
+                                stackTraceToSingleLineString(e));
+        messages.add(msg);
+        if(resultCode == ResultCode.SUCCESS)
+        {
+          resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
+        }
+      }
+    }
+
+    // Update the sort order only if changed.
+    if(!config.getVLVIndexSortOrder().equals(
+        cfg.getVLVIndexSortedSetCapacity()))
+    {
+      String[] sortAttrs = cfg.getVLVIndexSortOrder().split(" ");
+      SortKey[] sortKeys = new SortKey[sortAttrs.length];
+      OrderingMatchingRule[] orderingRules =
+          new OrderingMatchingRule[sortAttrs.length];
+      boolean[] ascending = new boolean[sortAttrs.length];
+      for(int i = 0; i < sortAttrs.length; i++)
+      {
+        try
+        {
+          if(sortAttrs[i].startsWith("-"))
+          {
+            ascending[i] = false;
+            sortAttrs[i] = sortAttrs[i].substring(1);
+          }
+          else
+          {
+            ascending[i] = true;
+            if(sortAttrs[i].startsWith("+"))
+            {
+              sortAttrs[i] = sortAttrs[i].substring(1);
+            }
+          }
+        }
+        catch(Exception e)
+        {
+          int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+          String msg = getMessage(msgID, sortKeys[i], name);
+          messages.add(msg);
+          if(resultCode == ResultCode.SUCCESS)
+          {
+            resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
+          }
+        }
+
+        AttributeType attrType =
+            DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase());
+        if(attrType == null)
+        {
+          int msgID = MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR;
+          String msg = getMessage(msgID, sortKeys[i], name);
+          messages.add(msg);
+          if(resultCode == ResultCode.SUCCESS)
+          {
+            resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
+          }
+        }
+        sortKeys[i] = new SortKey(attrType, ascending[i]);
+        orderingRules[i] = attrType.getOrderingMatchingRule();
+      }
+
+      this.sortOrder = new SortOrder(sortKeys);
+      this.comparator = new VLVKeyComparator(orderingRules, ascending);
+
+      // We have to close the database and open it using the new comparator.
+      entryContainer.exclusiveLock.lock();
+      try
+      {
+        this.close();
+        this.dbConfig.setBtreeComparator(this.comparator);
+        this.open();
+      }
+      catch(DatabaseException de)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(de));
+        if(resultCode == ResultCode.SUCCESS)
+        {
+          resultCode = DirectoryServer.getServerErrorResultCode();
+        }
+      }
+      finally
+      {
+        entryContainer.exclusiveLock.unlock();
+      }
+
+      adminActionRequired = true;
+    }
+
+
+    if(adminActionRequired)
+    {
+      trusted = false;
+      try
+      {
+        state.putIndexTrustState(null, this, false);
+      }
+      catch(DatabaseException de)
+      {
+        messages.add(StaticUtils.stackTraceToSingleLineString(de));
+        if(resultCode == ResultCode.SUCCESS)
+        {
+          resultCode = DirectoryServer.getServerErrorResultCode();
+        }
+      }
+    }
+
+    this.config = cfg;
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexBuilder.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexBuilder.java
new file mode 100644
index 0000000..caf413f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexBuilder.java
@@ -0,0 +1,324 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import static org.opends.server.util.StaticUtils.getFileForPath;
+import org.opends.server.types.*;
+
+import java.util.*;
+import java.io.*;
+
+import com.sleepycat.je.DatabaseException;
+import com.sleepycat.je.Transaction;
+
+/**
+ * This class is used to create an VLV vlvIndex for an import process.
+ * It is used as follows.
+ * <pre>
+ * startProcessing();
+ * processEntry(entry);
+ * processEntry(entry);
+ * ...
+ * stopProcessing();
+ * merge();
+ * </pre>
+ */
+public class VLVIndexBuilder implements IndexBuilder
+{
+  /**
+   * The import context.
+   */
+  private ImportContext importContext;
+
+  /**
+   * The vlvIndex database.
+   */
+  private VLVIndex vlvIndex;
+
+  /**
+   * The add write buffer.
+   */
+  TreeMap<SortValues,EntryID> addBuffer;
+
+  /**
+   * The delete write buffer.
+   */
+  TreeMap<SortValues,EntryID> delBuffer;
+
+  /**
+   * The write buffer size.
+   */
+  private int bufferSize;
+
+  /**
+   * Current output file number.
+   */
+  private int fileNumber = 0;
+
+  /**
+   * A unique prefix for temporary files to prevent conflicts.
+   */
+  private String fileNamePrefix;
+
+  /**
+   * Indicates whether we are replacing existing data or not.
+   */
+  private boolean replaceExisting = false;
+
+
+  private ByteArrayOutputStream addBytesStream = new ByteArrayOutputStream();
+  private ByteArrayOutputStream delBytesStream = new ByteArrayOutputStream();
+
+  private DataOutputStream addBytesDataStream;
+  private DataOutputStream delBytesDataStream;
+
+  /**
+   * A file name filter to identify temporary files we have written.
+   */
+  private FilenameFilter filter = new FilenameFilter()
+  {
+    public boolean accept(File d, String name)
+    {
+      return name.startsWith(fileNamePrefix);
+    }
+  };
+
+  /**
+   * Construct an vlvIndex builder.
+   *
+   * @param importContext The import context.
+   * @param vlvIndex The vlvIndex database we are writing.
+   * @param bufferSize The amount of memory available for buffering.
+   */
+  public VLVIndexBuilder(ImportContext importContext,
+                         VLVIndex vlvIndex, long bufferSize)
+  {
+    this.importContext = importContext;
+    this.vlvIndex = vlvIndex;
+    this.bufferSize = (int)bufferSize/100;
+    long tid = Thread.currentThread().getId();
+    fileNamePrefix = vlvIndex.getName() + "_" + tid + "_";
+    replaceExisting =
+        importContext.getLDIFImportConfig().appendToExistingData() &&
+            importContext.getLDIFImportConfig().replaceExistingEntries();
+    addBytesDataStream = new DataOutputStream(addBytesStream);
+    delBytesDataStream = new DataOutputStream(delBytesStream);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void startProcessing()
+  {
+    // Clean up any work files left over from a previous run.
+    File tempDir = getFileForPath(
+        importContext.getConfig().getBackendImportTempDirectory());
+    File[] files = tempDir.listFiles(filter);
+    if (files != null)
+    {
+      for (File f : files)
+      {
+        f.delete();
+      }
+    }
+
+    addBuffer = new TreeMap<SortValues,EntryID>();
+    delBuffer = new TreeMap<SortValues, EntryID>();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void processEntry(Entry oldEntry, Entry newEntry, EntryID entryID)
+      throws DatabaseException, IOException, DirectoryException
+  {
+    Transaction txn = null;
+    SortValues newValues = new SortValues(entryID, newEntry,
+                                          vlvIndex.sortOrder);
+    // Update the vlvIndex for this entry.
+    if (oldEntry != null)
+    {
+      if(vlvIndex.shouldInclude(oldEntry))
+      {
+      // This is an entry being replaced.
+      SortValues oldValues = new SortValues(entryID, oldEntry,
+                                            vlvIndex.sortOrder);
+      removeValues(oldValues, entryID);
+      }
+
+    }
+
+    if(vlvIndex.shouldInclude(newEntry))
+    {
+      insertValues(newValues, entryID);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void stopProcessing() throws IOException
+  {
+    flushBuffer();
+  }
+
+  /**
+   * Record the insertion of an entry ID.
+   * @param sortValues The sort values.
+   * @param entryID The entry ID.
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void insertValues(SortValues sortValues, EntryID entryID)
+      throws IOException
+  {
+    if (addBuffer.size() + delBuffer.size() >= bufferSize)
+    {
+      flushBuffer();
+    }
+
+    addBuffer.put(sortValues, entryID);
+  }
+
+  /**
+   * Record the deletion of an entry ID.
+   * @param sortValues The sort values to remove.
+   * @param entryID The entry ID.
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void removeValues(SortValues sortValues, EntryID entryID)
+      throws IOException
+  {
+    if (addBuffer.size() + delBuffer.size() >= bufferSize)
+    {
+      flushBuffer();
+    }
+
+    delBuffer.remove(sortValues);
+  }
+
+  /**
+   * Called when the buffer is full. It first sorts the buffer using the same
+   * key comparator used by the vlvIndex database. Then it merges all the
+   * IDs for the same key together and writes each key and its list of IDs
+   * to an intermediate binary file.
+   * A list of deleted IDs is only present if we are replacing existing entries.
+   *
+   * @throws IOException If an I/O error occurs while writing an intermediate
+   * file.
+   */
+  private void flushBuffer() throws IOException
+  {
+    if (addBuffer.size() + delBuffer.size() == 0)
+    {
+      return;
+    }
+
+    // Start a new file.
+    fileNumber++;
+    String fileName = fileNamePrefix + String.valueOf(fileNumber) + "_add";
+    File file = new File(getFileForPath(
+        importContext.getConfig().getBackendImportTempDirectory()),
+                         fileName);
+    BufferedOutputStream bufferedStream =
+        new BufferedOutputStream(new FileOutputStream(file));
+    DataOutputStream dataStream = new DataOutputStream(bufferedStream);
+
+    try
+    {
+      for (SortValues values : addBuffer.keySet())
+      {
+        dataStream.writeLong(values.getEntryID());
+        for(AttributeValue value : values.getValues())
+        {
+          if(value != null)
+          {
+            byte[] valueBytes = value.getValueBytes();
+            dataStream.writeInt(valueBytes.length);
+            dataStream.write(valueBytes);
+          }
+          else
+          {
+            dataStream.writeInt(0);
+          }
+        }
+      }
+    }
+    finally
+    {
+      dataStream.close();
+    }
+
+    if (replaceExisting)
+    {
+      fileName = fileNamePrefix + String.valueOf(fileNumber) + "_del";
+      file = new File(getFileForPath(
+          importContext.getConfig().getBackendImportTempDirectory()),
+                      fileName);
+      bufferedStream =
+          new BufferedOutputStream(new FileOutputStream(file));
+      dataStream = new DataOutputStream(bufferedStream);
+
+      try
+      {
+
+        for (SortValues values : delBuffer.keySet())
+        {
+          dataStream.writeLong(values.getEntryID());
+          for(AttributeValue value : values.getValues())
+          {
+            byte[] valueBytes = value.getValueBytes();
+            dataStream.writeInt(valueBytes.length);
+            dataStream.write(valueBytes);
+          }
+        }
+      }
+      finally
+      {
+        dataStream.close();
+      }
+    }
+
+    addBuffer = new TreeMap<SortValues,EntryID>();
+    delBuffer = new TreeMap<SortValues, EntryID>();
+  }
+
+  /**
+   * Get a string that identifies this vlvIndex builder.
+   *
+   * @return A string that identifies this vlvIndex builder.
+   */
+  public String toString()
+  {
+    return vlvIndex.toString() + " builder";
+  }
+}
+
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexMergeThread.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexMergeThread.java
new file mode 100644
index 0000000..fde7ce4
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndexMergeThread.java
@@ -0,0 +1,481 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.loggers.debug.DebugTracer;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import org.opends.server.admin.std.server.JEBackendCfg;
+import org.opends.server.types.*;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import static org.opends.server.util.StaticUtils.getFileForPath;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_INDEX_MERGE_NO_DATA;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_INDEX_MERGE_START;
+import static org.opends.server.messages.JebMessages.
+    MSGID_JEB_INDEX_MERGE_COMPLETE;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
+import java.util.*;
+import java.io.*;
+
+import com.sleepycat.je.Transaction;
+
+/**
+ * A thread to merge a set of intermediate files from an vlvIndex builder
+ * into an vlvIndex database.
+ */
+public class VLVIndexMergeThread extends DirectoryThread
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+  /**
+   * The buffer size to use when reading data from disk.
+   */
+  private static final int INPUT_STREAM_BUFFER_SIZE = 65536;
+
+  /**
+   * The configuration of the JE backend containing the vlvIndex.
+   */
+  JEBackendCfg config;
+
+  /**
+   * The LDIF import configuration, which indicates whether we are
+   * appending to existing data.
+   */
+  LDIFImportConfig ldifImportConfig;
+
+  /**
+   * The vlvIndex database being written.
+   */
+  VLVIndex vlvIndex;
+
+  /**
+   * The name of the vlvIndex for use in file names and log messages.
+   */
+  String indexName;
+
+  /**
+   * Indicates whether we are replacing existing data or not.
+   */
+  private boolean replaceExisting = false;
+
+  private List<DataInputStream> addDataStreams;
+  private List<DataInputStream> delDataStreams;
+
+  /**
+   * A weak reference hash map used to cache last sort values read from files.
+   */
+  private HashMap<DataInputStream,SortValues> lastAddValues =
+      new HashMap<DataInputStream,SortValues>();
+
+  private HashMap<DataInputStream,SortValues> lastDelValues =
+      new HashMap<DataInputStream,SortValues>();
+
+
+  /**
+   * A file name filter to identify temporary files we have written.
+   */
+  private FilenameFilter filter = new FilenameFilter()
+  {
+    public boolean accept(File d, String name)
+    {
+      return name.startsWith(vlvIndex.getName());
+    }
+  };
+
+  /**
+   * Create a new vlvIndex merge thread.
+   * @param config The configuration of the JE backend containing the vlvIndex.
+   * @param ldifImportConfig The LDIF import configuration, which indicates
+   * whether we are appending to existing data.
+   * @param vlvIndex The vlvIndex database to be written.
+   */
+  VLVIndexMergeThread(JEBackendCfg config,
+                      LDIFImportConfig ldifImportConfig,
+                      VLVIndex vlvIndex)
+  {
+    super("Index Merge Thread " + vlvIndex.getName());
+
+    this.config = config;
+    this.ldifImportConfig = ldifImportConfig;
+    this.vlvIndex = vlvIndex;
+    replaceExisting =
+        ldifImportConfig.appendToExistingData() &&
+            ldifImportConfig.replaceExistingEntries();
+    addDataStreams = new ArrayList<DataInputStream>();
+    delDataStreams = new ArrayList<DataInputStream>();
+    lastAddValues = new HashMap<DataInputStream, SortValues>();
+    lastDelValues = new HashMap<DataInputStream, SortValues>();
+  }
+
+  /**
+   * Run this thread.
+   */
+  public void run()
+  {
+    try
+    {
+      merge();
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * The merge phase builds the vlvIndex from intermediate files written
+   * during entry processing. Each line of an intermediate file has data for
+   * one vlvIndex key and the keys are in order. For each vlvIndex key, the data
+   * from each intermediate file containing a line for that key must be merged
+   * and written to the vlvIndex.
+   * @throws Exception If an error occurs.
+   */
+  public void merge() throws Exception
+  {
+    // Open all the files.
+    File tempDir = getFileForPath(config.getBackendImportTempDirectory());
+    File[] files = tempDir.listFiles(filter);
+
+    if (files == null || files.length == 0)
+    {
+      int msgID = MSGID_JEB_INDEX_MERGE_NO_DATA;
+      String message = getMessage(msgID, vlvIndex.getName());
+      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
+               message, msgID);
+      return;
+    }
+
+    if (debugEnabled())
+    {
+      int msgID = MSGID_JEB_INDEX_MERGE_START;
+      String message = getMessage(msgID, files.length, vlvIndex.getName());
+      TRACER.debugInfo(message);
+    }
+
+    Transaction txn = null;
+
+    try
+    {
+      for (int i = 0; i < files.length; i++)
+      {
+        // Open a reader for this file.
+        BufferedInputStream bufferedStream =
+            new BufferedInputStream(new FileInputStream(files[i]),
+                                    INPUT_STREAM_BUFFER_SIZE);
+        DataInputStream dis = new DataInputStream(bufferedStream);
+        if(files[i].getName().endsWith("_add"))
+        {
+          addDataStreams.add(dis);
+        }
+        else if(files[i].getName().endsWith("_del"))
+        {
+          delDataStreams.add(dis);
+        }
+      }
+
+      while(true)
+      {
+        SortValuesSet currentSet = null;
+        SortValues maxKey = null;
+        // Get a set by using the smallest sort values
+        SortValues addValue = readNextAdd(maxKey);
+
+        // Process deletes first for this set
+        if(replaceExisting)
+        {
+          SortValues delValue = readNextDel(maxKey);
+          if(delValue != null)
+          {
+            if(currentSet == null)
+            {
+              if(addValue == null || delValue.compareTo(addValue) < 0)
+              {
+                // Set the current set using the del value.
+                currentSet = vlvIndex.getSortValuesSet(txn,
+                                                       delValue.getEntryID(),
+                                                       delValue.getValues());
+              }
+              else
+              {
+                // Set the current set using the add value.
+                currentSet = vlvIndex.getSortValuesSet(txn,
+                                                       addValue.getEntryID(),
+                                                       addValue.getValues());
+              }
+              maxKey = currentSet.getKeySortValues();
+            }
+          }
+
+          while(delValue != null)
+          {
+            currentSet.remove(delValue.getEntryID(), delValue.getValues());
+            delValue = readNextDel(maxKey);
+          }
+        }
+
+        if(addValue != null)
+        {
+          if(currentSet == null)
+          {
+            currentSet = vlvIndex.getSortValuesSet(txn, addValue.getEntryID(),
+                                                   addValue.getValues());
+            maxKey = currentSet.getKeySortValues();
+          }
+
+          while(addValue != null)
+          {
+            currentSet.add(addValue.getEntryID(), addValue.getValues());
+            if(currentSet.size() > vlvIndex.getSortedSetCapacity())
+            {
+              // Need to split the set as it has exceeded the entry limit.
+              SortValuesSet splitSortValuesSet =
+                  currentSet.split(currentSet.size() / 2);
+              // Find where the set split and see if the last added values
+              // is before or after the split.
+              SortValues newKey = currentSet.getKeySortValues();
+
+              if(debugEnabled())
+              {
+                TRACER.debugInfo("SortValuesSet with key %s has reached" +
+                    " the entry size of %d. Spliting into two sets with " +
+                    " keys %s and %s.", maxKey, currentSet.size(), newKey,
+                                        maxKey);
+              }
+
+              if(addValue.compareTo(newKey) < 0)
+              {
+                // The last added values is before the split so we have to
+                // keep adding to it.
+                vlvIndex.putSortValuesSet(txn, splitSortValuesSet);
+                maxKey = newKey;
+              }
+              else
+              {
+                // The last added values is after the split so we can add to
+                // the newly split set.
+                vlvIndex.putSortValuesSet(txn, currentSet);
+                currentSet = splitSortValuesSet;
+              }
+            }
+            addValue = readNextAdd(maxKey);
+          }
+        }
+
+        // We should have made all the modifications to this set. Store it back
+        // to database.
+        vlvIndex.putSortValuesSet(txn, currentSet);
+
+        if(maxKey == null)
+        {
+          // If we reached here, we should have processed all the sets and
+          // there should be nothing left to add or delete.
+          break;
+        }
+      }
+
+      if(!ldifImportConfig.appendToExistingData())
+      {
+        vlvIndex.setTrusted(txn, true);
+      }
+    }
+    finally
+    {
+      for(DataInputStream stream : addDataStreams)
+      {
+        stream.close();
+      }
+
+      for(DataInputStream stream : delDataStreams)
+      {
+        stream.close();
+      }
+
+      // Delete all the files.
+      if (files != null)
+      {
+        for (File f : files)
+        {
+          f.delete();
+        }
+      }
+    }
+
+    if (debugEnabled())
+    {
+      int msgID = MSGID_JEB_INDEX_MERGE_COMPLETE;
+      String message = getMessage(msgID, vlvIndex.getName());
+      TRACER.debugInfo(message);
+    }
+  }
+
+  /**
+   * Reads the next sort values from the files that is smaller then the max.
+   * @throws IOException If an I/O error occurs while reading the input file.
+   */
+  private SortValues readNextAdd(SortValues maxValues)
+      throws IOException
+  {
+    for(DataInputStream dataInputStream : addDataStreams)
+    {
+      if(lastAddValues.get(dataInputStream) == null)
+      {
+        try
+        {
+          SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
+          EntryID id = new EntryID(dataInputStream.readLong());
+          AttributeValue[] attrValues =
+              new AttributeValue[sortKeys.length];
+          for(int i = 0; i < sortKeys.length; i++)
+          {
+            SortKey sortKey = sortKeys[i];
+            int length = dataInputStream.readInt();
+            if(length > 0)
+            {
+              byte[] valueBytes = new byte[length];
+              if(length == dataInputStream.read(valueBytes, 0, length))
+              {
+                attrValues[i] =
+                    new AttributeValue(sortKey.getAttributeType(),
+                                       new ASN1OctetString(valueBytes));
+              }
+            }
+
+          }
+          lastAddValues.put(dataInputStream,
+                            new SortValues(id, attrValues, vlvIndex.sortOrder));
+        }
+        catch (EOFException e)
+        {
+          continue;
+        }
+      }
+    }
+
+    Map.Entry<DataInputStream, SortValues> smallestEntry = null;
+    for(Map.Entry<DataInputStream, SortValues> entry :
+        lastAddValues.entrySet())
+    {
+      if(smallestEntry == null ||
+          entry.getValue().compareTo(smallestEntry.getValue()) < 0)
+      {
+        smallestEntry = entry;
+      }
+    }
+
+    if(smallestEntry != null)
+    {
+      SortValues smallestValues = smallestEntry.getValue();
+      if(maxValues == null || smallestValues.compareTo(maxValues) <= 0)
+      {
+        lastAddValues.remove(smallestEntry.getKey());
+        return smallestValues;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Reads the next sort values from the files that is smaller then the max.
+   * @throws IOException If an I/O error occurs while reading the input file.
+   */
+  private SortValues readNextDel(SortValues maxValues)
+      throws IOException
+  {
+    for(DataInputStream dataInputStream : delDataStreams)
+    {
+      if(lastDelValues.get(dataInputStream) == null)
+      {
+        try
+        {
+          EntryID id = new EntryID(dataInputStream.readLong());
+          AttributeValue[] attrValues =
+              new AttributeValue[vlvIndex.sortOrder.getSortKeys().length];
+          int i = 0;
+          for(SortKey sortKey : vlvIndex.sortOrder.getSortKeys())
+          {
+            int length = dataInputStream.readInt();
+            if(length > 0)
+            {
+              byte[] valueBytes = new byte[length];
+              if(length == dataInputStream.read(valueBytes, 0, length))
+              {
+                attrValues[i] =
+                    new AttributeValue(sortKey.getAttributeType(),
+                                       new ASN1OctetString(valueBytes));
+              }
+            }
+          }
+          lastDelValues.put(dataInputStream,
+                            new SortValues(id, attrValues,
+                                           vlvIndex.sortOrder));
+        }
+        catch (EOFException e)
+        {
+          continue;
+        }
+      }
+    }
+
+    Map.Entry<DataInputStream, SortValues> smallestEntry = null;
+    for(Map.Entry<DataInputStream, SortValues> entry :
+        lastDelValues.entrySet())
+    {
+      if(smallestEntry == null ||
+          entry.getValue().compareTo(smallestEntry.getValue()) < 0)
+      {
+        smallestEntry = entry;
+      }
+    }
+
+    if(smallestEntry != null)
+    {
+      SortValues smallestValues = smallestEntry.getValue();
+      if(maxValues == null || smallestValues.compareTo(maxValues) <= 0)
+      {
+        lastDelValues.remove(smallestEntry.getKey());
+        return smallestValues;
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java
new file mode 100644
index 0000000..1c5e75c
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java
@@ -0,0 +1,352 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import org.opends.server.api.OrderingMatchingRule;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DirectoryException;
+
+import java.util.Comparator;
+import java.io.Serializable;
+
+import com.sleepycat.je.DatabaseException;
+
+/**
+ * This class is used to compare the keys used in a VLV index. Each key is
+ * made up the sort values and the entry ID of the largest entry in the sorted
+ * set stored in the data for the key.
+ */
+public class VLVKeyComparator implements Comparator<byte[]>, Serializable
+{
+  /**
+   * The serial version identifier required to satisfy the compiler because this
+   * class implements the <CODE>java.io.Serializable</CODE> interface.  This
+   * value was generated using the <CODE>serialver</CODE> command-line utility
+   * included with the Java SDK.
+   */
+  static final long serialVersionUID = 1585167927344130604L;
+
+  private OrderingMatchingRule[] orderingRules;
+
+  private boolean[] ascending;
+
+  /**
+   * Construst a new VLV Key Comparator object.
+   *
+   * @param orderingRules The array of ordering rules to use when comparing
+   *                      the decoded values in the key.
+   * @param ascending     The array of booleans indicating the ordering for
+   *                      each value.
+   */
+  public VLVKeyComparator(OrderingMatchingRule[] orderingRules,
+                          boolean[] ascending)
+  {
+    this.orderingRules = orderingRules;
+    this.ascending = ascending;
+  }
+
+  /**
+   * Compares the contents of the provided byte arrays to determine their
+   * relative order. A key in the VLV index contains the sorted attribute values
+   * in order followed by the 8 byte entry ID. A attribute value of length 0
+   * means that value is null and the attribute type was not part of the entry.
+   * A null value is always considered greater then a non null value. If all
+   * attribute values are the same, the entry ID will be used to determine the
+   * ordering.
+   *
+   * When comparing partial keys (ie. keys with only the first attribute value
+   * encoded for evaluating VLV assertion value offsets or keys with no entry
+   * IDs), only information available in both byte keys will be used to
+   * determine the ordering. If all available information is the same, 0 will
+   * be returned.
+   *
+   * @param  b1  The first byte array to use in the comparison.
+   * @param  b2  The second byte array to use in the comparison.
+   *
+   * @return  A negative integer if <CODE>b1</CODE> should come before
+   *          <CODE>b2</CODE> in ascending order, a positive integer if
+   *          <CODE>b1</CODE> should come after <CODE>b2</CODE> in ascending
+   *          order, or zero if there is no difference between the values with
+   *          regard to ordering.
+   */
+  public int compare(byte[] b1, byte[] b2)
+  {
+    // A 0 length byte array is a special key used for the unbound max
+    // sort values set. It always comes after a non length byte array.
+    if(b1.length == 0)
+    {
+      if(b2.length == 0)
+      {
+        return 0;
+      }
+      else
+      {
+        return 1;
+      }
+    }
+    else if(b2.length == 0)
+    {
+      return -1;
+    }
+
+    int b1Pos = 0;
+    int b2Pos = 0;
+    for (int j=0;
+         j < orderingRules.length && b1Pos < b1.length && b2Pos < b2.length;
+         j++)
+    {
+      int b1Length = b1[b1Pos] & 0x7F;
+      if (b1[b1Pos++] != b1Length)
+      {
+        int b1NumLengthBytes = b1Length;
+        b1Length = 0;
+        for (int k=0; k < b1NumLengthBytes; k++, b1Pos++)
+        {
+          b1Length = (b1Length << 8) |
+              (b1[b1Pos] & 0xFF);
+        }
+      }
+
+      int b2Length = b2[b2Pos] & 0x7F;
+      if (b2[b2Pos++] != b2Length)
+      {
+        int b2NumLengthBytes = b2Length;
+        b2Length = 0;
+        for (int k=0; k < b2NumLengthBytes; k++, b2Pos++)
+        {
+          b2Length = (b2Length << 8) |
+              (b2[b2Pos] & 0xFF);
+        }
+      }
+
+      byte[] b1Bytes;
+      byte[] b2Bytes;
+      if(b1Length > 0)
+      {
+        b1Bytes = new byte[b1Length];
+        System.arraycopy(b1, b1Pos, b1Bytes, 0, b1Length);
+        b1Pos += b1Length;
+      }
+      else
+      {
+        b1Bytes = null;
+      }
+
+      if(b2Length > 0)
+      {
+        b2Bytes = new byte[b2Length];
+        System.arraycopy(b2, b2Pos, b2Bytes, 0, b2Length);
+        b2Pos += b2Length;
+      }
+      else
+      {
+        b2Bytes = null;
+      }
+
+      // A null value will always come after a non-null value.
+      if (b1Bytes == null)
+      {
+        if (b2Bytes == null)
+        {
+          continue;
+        }
+        else
+        {
+          return 1;
+        }
+      }
+      else if (b2Bytes == null)
+      {
+        return -1;
+      }
+
+      int result;
+      if(ascending[j])
+      {
+        result = orderingRules[j].compare(b1Bytes, b2Bytes);
+      }
+      else
+      {
+        result = orderingRules[j].compare(b2Bytes, b1Bytes);
+      }
+
+      if(result != 0)
+      {
+        return result;
+      }
+    }
+
+    // If we've gotten here, then we can't tell a difference between the sets
+    // of available values, so sort based on entry ID if its in the key.
+
+    if(b1Pos + 8 < b1.length && b2Pos + 8 < b2.length)
+    {
+      long b1ID = 0;
+      for (int i = b1Pos; i < b1Pos + 8; i++)
+      {
+        b1ID <<= 8;
+        b1ID |= (b1[i] & 0xFF);
+      }
+
+      long b2ID = 0;
+      for (int i = b2Pos; i < b2Pos + 8; i++)
+      {
+        b2ID <<= 8;
+        b2ID |= (b2[i] & 0xFF);
+      }
+
+      long idDifference = (b1ID - b2ID);
+      if (idDifference < 0)
+      {
+        return -1;
+      }
+      else if (idDifference > 0)
+      {
+        return 1;
+      }
+      else
+      {
+        return 0;
+      }
+    }
+
+    // If we've gotten here, then we can't tell the difference between the sets
+    // of available values and entry IDs are not all available, so just return
+    // 0
+    return 0;
+
+  }
+
+  /**
+   * Compares the contents in the provided values set with the given values to
+   * determine their relative order. A null value is always considered greater
+   * then a non null value. If all attribute values are the same, the entry ID
+   * will be used to determine the ordering.
+   *
+   * If the given attribute values array does not contain all the values in the
+   * sort order, any missing values will be considered as a unknown or
+   * wildcard value instead of a nonexistant value. When comparing partial
+   * information, only values available in both the values set and the
+   * given values will be used to determine the ordering. If all available
+   * information is the same, 0 will be returned.
+   *
+   * @param  set  The sort values set to containing the values.
+   * @param  index The index of the values in the set.
+   * @param  entryID The entry ID to use in the comparasion.
+   * @param  values The values to use in the comparasion.
+   *
+   * @return  A negative integer if the values in the set should come before
+   *          the given values in ascending order, a positive integer if
+   *          the values in the set should come after the given values in
+   *          ascending order, or zero if there is no difference between the
+   *          values with regard to ordering.
+   * @throws DatabaseException If an error occurs during an operation on a
+   * JE database.
+   * @throws JebException If an error occurs during an operation on a
+   * JE database.
+   * @throws DirectoryException  If an error occurs while trying to
+   *                              normalize the value (e.g., if it is
+   *                              not acceptable for use with the
+   *                              associated equality matching rule).
+   */
+  public int compareValuesInSet(SortValuesSet set, int index,
+                                long entryID, AttributeValue[] values)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    for (int j=0; j < orderingRules.length; j++)
+    {
+      if(j >= values.length)
+      {
+        break;
+      }
+
+      byte[] b1Bytes = set.getValue((index * orderingRules.length) + j);
+      byte[] b2Bytes = null;
+
+      if(values[j] != null)
+      {
+        b2Bytes = values[j].getNormalizedValueBytes();
+      }
+
+      // A null value will always come after a non-null value.
+      if (b1Bytes == null)
+      {
+        if (b2Bytes == null)
+        {
+          continue;
+        }
+        else
+        {
+          return 1;
+        }
+      }
+      else if (b2Bytes == null)
+      {
+        return -1;
+      }
+
+      int result;
+      if(ascending[j])
+      {
+        result = orderingRules[j].compare(b1Bytes, b2Bytes);
+      }
+      else
+      {
+        result = orderingRules[j].compare(b2Bytes, b1Bytes);
+      }
+
+      if(result != 0)
+      {
+        return result;
+      }
+    }
+
+    if(entryID != -1)
+    {
+      // If we've gotten here, then we can't tell a difference between the sets
+      // of values, so sort based on entry ID.
+
+      long idDifference = (set.getEntryIDs()[index] - entryID);
+      if (idDifference < 0)
+      {
+        return -1;
+      }
+      else if (idDifference > 0)
+      {
+        return 1;
+      }
+      else
+      {
+        return 0;
+      }
+    }
+
+    // If we've gotten here, then we can't tell the difference between the sets
+    // of available values and the entry ID is not available. Just return 0.
+    return 0;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
index 2b74703..4ce1c93 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -44,21 +44,10 @@
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
-import org.opends.server.types.ByteString;
-import org.opends.server.types.ConditionResult;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
-import org.opends.server.types.ErrorLogCategory;
-import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.SearchFilter;
 import org.opends.server.util.StaticUtils;
 import org.opends.server.util.ServerConstants;
 
-import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.messages.JebMessages.*;
 import java.util.ArrayList;
@@ -174,6 +163,12 @@
    * A list of the attribute indexes to be verified.
    */
   ArrayList<AttributeIndex> attrIndexList = new ArrayList<AttributeIndex>();
+
+  /**
+   * A list of the VLV indexes to be verified.
+   */
+  ArrayList<VLVIndex> vlvIndexList = new ArrayList<VLVIndex>();
+
 /**
  * The types of indexes that are verifiable.
  */
@@ -200,9 +195,10 @@
    * @return The error count.
    * @throws DatabaseException If an error occurs in the JE database.
    * @throws JebException If an error occurs in the JE backend.
+   * @throws DirectoryException If an error occurs while verifying the backend.
    */
   public long verifyBackend(RootContainer rootContainer, Entry statEntry) throws
-      DatabaseException, JebException
+      DatabaseException, JebException, DirectoryException
   {
     this.rootContainer = rootContainer;
     EntryContainer entryContainer =
@@ -250,6 +246,26 @@
           {
             verifyID2Subtree = true;
           }
+          else if(lowerName.startsWith("vlv."))
+          {
+            if(lowerName.length() < 5)
+            {
+              int msgID = MSGID_JEB_VLV_INDEX_NOT_CONFIGURED;
+              String msg = getMessage(msgID, lowerName);
+              throw new JebException(msgID, msg);
+            }
+
+            VLVIndex vlvIndex =
+                entryContainer.getVLVIndex(lowerName.substring(4));
+            if(vlvIndex == null)
+            {
+              int msgID = MSGID_JEB_VLV_INDEX_NOT_CONFIGURED;
+              String msg = getMessage(msgID, lowerName.substring(4));
+              throw new JebException(msgID, msg);
+            }
+
+            vlvIndexList.add(vlvIndex);
+          }
           else
           {
             AttributeType attrType =
@@ -304,6 +320,12 @@
         else
         {
           iterateID2Entry();
+
+          // Make sure the vlv indexes are in correct order.
+          for(VLVIndex vlvIndex : vlvIndexList)
+          {
+            iterateVLVIndex(vlvIndex, false);
+          }
         }
       }
       finally
@@ -507,8 +529,10 @@
    *
    * @throws JebException If an error occurs in the JE backend.
    * @throws DatabaseException If an error occurs in the JE database.
+   * @throws DirectoryException If an error occurs reading values in the index.
    */
-  private void iterateIndex() throws JebException, DatabaseException
+  private void iterateIndex()
+      throws JebException, DatabaseException, DirectoryException
   {
     if (verifyDN2ID)
     {
@@ -524,17 +548,23 @@
     }
     else
     {
-      AttributeIndex attrIndex = attrIndexList.get(0);
-      iterateAttrIndex(attrIndex.getAttributeType(),
-              attrIndex.equalityIndex, IndexType.EQ );
-      iterateAttrIndex(attrIndex.getAttributeType(),
-              attrIndex.presenceIndex, IndexType.PRES);
-      iterateAttrIndex(attrIndex.getAttributeType(),
-              attrIndex.substringIndex, IndexType.SUBSTRING);
-      iterateAttrIndex(attrIndex.getAttributeType(),
-              attrIndex.orderingIndex, IndexType.ORDERING);
-      iterateAttrIndex(attrIndex.getAttributeType(),
-              attrIndex.approximateIndex, IndexType.APPROXIMATE);
+      if(attrIndexList.size() > 0)
+      {
+        AttributeIndex attrIndex = attrIndexList.get(0);
+        iterateAttrIndex(attrIndex.getAttributeType(),
+                         attrIndex.equalityIndex, IndexType.EQ );
+        iterateAttrIndex(attrIndex.getAttributeType(),
+                         attrIndex.presenceIndex, IndexType.PRES);
+        iterateAttrIndex(attrIndex.getAttributeType(),
+                         attrIndex.substringIndex, IndexType.SUBSTRING);
+        iterateAttrIndex(attrIndex.getAttributeType(),
+                         attrIndex.orderingIndex, IndexType.ORDERING);
+        iterateAttrIndex(attrIndex.getAttributeType(),
+                         attrIndex.approximateIndex, IndexType.APPROXIMATE);
+      } else if(vlvIndexList.size() > 0)
+      {
+        iterateVLVIndex(vlvIndexList.get(0), true);
+      }
     }
   }
 
@@ -976,6 +1006,130 @@
   }
 
   /**
+   * Iterate through the entries in a VLV index to perform a check for index
+   * cleanliness.
+   *
+   * @param vlvIndex The VLV index to perform the check against.
+   * @param verifyID True to verify the IDs against id2entry.
+   * @throws JebException If an error occurs in the JE backend.
+   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws DirectoryException If an error occurs reading values in the index.
+   */
+  private void iterateVLVIndex(VLVIndex vlvIndex, boolean verifyID)
+      throws JebException, DatabaseException, DirectoryException
+  {
+    if(vlvIndex == null)
+    {
+      return;
+    }
+
+    Cursor cursor = vlvIndex.openCursor(null, new CursorConfig());
+    try
+    {
+      DatabaseEntry key = new DatabaseEntry();
+      OperationStatus status;
+      LockMode lockMode = LockMode.DEFAULT;
+      DatabaseEntry data = new DatabaseEntry();
+
+      status = cursor.getFirst(key, data, lockMode);
+      SortValues lastValues = null;
+      while(status == OperationStatus.SUCCESS)
+      {
+        SortValuesSet sortValuesSet =
+            new SortValuesSet(key.getData(), data.getData(), vlvIndex,
+                              id2entry);
+        for(int i = 0; i < sortValuesSet.getEntryIDs().length; i++)
+        {
+          keyCount++;
+          SortValues values = sortValuesSet.getSortValues(i);
+          if(lastValues != null && lastValues.compareTo(values) >= 1)
+          {
+            // Make sure the values is larger then the previous one.
+            if(debugEnabled())
+            {
+              TRACER.debugError("Values %s and %s are incorrectly ordered",
+                                lastValues, values, keyDump(vlvIndex,
+                                          sortValuesSet.getKeySortValues()));
+            }
+            errorCount++;
+          }
+          if(i == sortValuesSet.getEntryIDs().length - 1 &&
+              key.getData().length != 0)
+          {
+            // If this is the last one in a bounded set, make sure it is the
+            // same as the database key.
+            byte[] encodedKey = vlvIndex.encodeKey(values.getEntryID(),
+                                                   values.getValues());
+            if(!Arrays.equals(key.getData(), encodedKey))
+            {
+              if(debugEnabled())
+              {
+                TRACER.debugError("Incorrect key for SortValuesSet in VLV " +
+                    "index %s. Last values bytes %s, Key bytes %s",
+                                  vlvIndex.getName(), encodedKey, key);
+              }
+              errorCount++;
+            }
+          }
+          lastValues = values;
+
+          if(verifyID)
+          {
+            Entry entry;
+            EntryID id = new EntryID(values.getEntryID());
+            try
+            {
+              entry = id2entry.get(null, id);
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+              errorCount++;
+              continue;
+            }
+
+            if (entry == null)
+            {
+              errorCount++;
+              if (debugEnabled())
+              {
+                TRACER.debugError("Reference to unknown ID %d%n%s",
+                                  id.longValue(),
+                                  keyDump(vlvIndex,
+                                          sortValuesSet.getKeySortValues()));
+              }
+              continue;
+            }
+
+            SortValues entryValues =
+                new SortValues(id, entry, vlvIndex.sortOrder);
+            if(entryValues.compareTo(values) != 0)
+            {
+              errorCount++;
+              if(debugEnabled())
+              {
+                TRACER.debugError("Reference to entry ID %d " +
+                    "which does not match the values%n%s",
+                                  id.longValue(),
+                                  keyDump(vlvIndex,
+                                          sortValuesSet.getKeySortValues()));
+              }
+            }
+          }
+        }
+        status = cursor.getNext(key, data, lockMode);
+      }
+    }
+    finally
+    {
+      cursor.close();
+    }
+  }
+
+  /**
    * Iterate through the entries in an attribute index to perform a check for
    * index cleanliness.
    * @param attrType The attribute type of the index to be checked.
@@ -1244,7 +1398,7 @@
     {
       verifyID2Subtree(entryID, entry);
     }
-    verifyAttrIndex(entryID, entry);
+    verifyIndex(entryID, entry);
   }
 
   /**
@@ -1506,12 +1660,49 @@
   }
 
   /**
+   * Construct a printable string from a raw key value.
+   *
+   * @param vlvIndex The vlvIndex database containing the key value.
+   * @param keySortValues THe sort values that is being used as the key.
+   * @return A string that may be logged or printed.
+   */
+  private String keyDump(VLVIndex vlvIndex, SortValues keySortValues)
+  {
+/*
+    String str;
+    try
+    {
+      str = new String(keyBytes, "UTF-8");
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      str = StaticUtils.bytesToHex(keyBytes);
+    }
+    return str;
+*/
+    StringBuilder buffer = new StringBuilder(128);
+    buffer.append("File: ");
+    buffer.append(vlvIndex.toString());
+    buffer.append(ServerConstants.EOL);
+    buffer.append("Key (last sort values):");
+    if(keySortValues == null)
+    {
+      buffer.append("UNBOUNDED (0x00)");
+    }
+    else
+    {
+      buffer.append(keySortValues.toString());
+    }
+    return buffer.toString();
+  }
+
+  /**
    * Check that an attribute index is complete for a given entry.
    *
    * @param entryID The entry ID.
    * @param entry The entry to be checked.
    */
-  private void verifyAttrIndex(EntryID entryID, Entry entry)
+  private void verifyIndex(EntryID entryID, Entry entry)
   {
     for (AttributeIndex attrIndex : attrIndexList)
     {
@@ -1538,6 +1729,67 @@
         }
       }
     }
+
+    for (VLVIndex vlvIndex : vlvIndexList)
+    {
+      try
+      {
+        if(vlvIndex.shouldInclude(entry))
+        {
+          if(!vlvIndex.containsValues(null, entryID.longValue(),
+                                  vlvIndex.getSortValues(entry)))
+          {
+            if(debugEnabled())
+            {
+              TRACER.debugError("Missing entry %s in VLV index %s",
+                                entry.getDN().toString(),
+                                vlvIndex.getName());
+            }
+            errorCount++;
+          }
+        }
+      }
+      catch (DirectoryException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+          TRACER.debugError("Error checking entry %s against filter or " +
+              "base DN for VLV index %s: %s",
+                     entry.getDN().toString(),
+                     vlvIndex.getName(),
+                     e.getErrorMessage());
+        }
+        errorCount++;
+      }
+      catch (DatabaseException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+          TRACER.debugError("Error reading VLV index %s for entry %s: %s",
+                     vlvIndex.getName(),
+                     entry.getDN().toString(),
+                     StaticUtils.getBacktrace(e));
+        }
+        errorCount++;
+      }
+      catch (JebException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+          TRACER.debugError("Error reading VLV index %s for entry %s: %s",
+                     vlvIndex.getName(),
+                     entry.getDN().toString(),
+                     StaticUtils.getBacktrace(e));
+        }
+        errorCount++;
+      }
+    }
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java
index 75509b7..8c6f184 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/JebMessages.java
@@ -1239,6 +1239,29 @@
       CATEGORY_MASK_JEB | SEVERITY_MASK_INFORMATIONAL | 159;
 
   /**
+   * The message ID used to indicate an invalid sort attribute defined for a
+   * VLV index.
+   */
+  public static final int MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR =
+       CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 160;
+
+  /**
+   * The message ID used to indicate a bad search filter defined for a
+   * VLV index.
+   */
+  public static final int MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER =
+      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 161;
+
+  /**
+   * The message ID of an error indicating that there is no VLV index
+   * configured for an name that was provided to an index
+   * verification job.  This message takes one string argument which is the
+   * VLV index name.
+   */
+  public static final int MSGID_JEB_VLV_INDEX_NOT_CONFIGURED =
+      CATEGORY_MASK_JEB | SEVERITY_MASK_MILD_ERROR | 162;
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -1604,5 +1627,13 @@
                     "Processing LDIF");
     registerMessage(MSGID_JEB_IMPORT_LDIF_END,
                     "End of LDIF reached");
+    registerMessage(MSGID_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR,
+                    "Sort attribute %s for VLV index %s is not defined in " +
+                    "the server schema");
+    registerMessage(MSGID_JEB_CONFIG_VLV_INDEX_BAD_FILTER,
+                    "An error occured while parsing the search filter %s " +
+                    "defined for VLV index %s: %s");
+    registerMessage(MSGID_JEB_VLV_INDEX_NOT_CONFIGURED,
+                    "There is no VLV index configured with name '%s'");
   }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/VerifyIndex.java b/opendj-sdk/opends/src/server/org/opends/server/tools/VerifyIndex.java
index dccec9e..d8eddd5 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/VerifyIndex.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/VerifyIndex.java
@@ -61,6 +61,7 @@
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
+import org.opends.server.util.StaticUtils;
 import static org.opends.server.tools.ToolConstants.*;
 import org.opends.server.admin.std.server.BackendCfg;
 
@@ -546,7 +547,8 @@
     catch (Exception e)
     {
       int    msgID   = MSGID_VERIFYINDEX_ERROR_DURING_VERIFY;
-      String message = getMessage(msgID, getExceptionMessage(e));
+      String message = getMessage(msgID,
+                                  StaticUtils.stackTraceToSingleLineString(e));
       logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR, message,
                msgID);
       returnCode = 1;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/SortKey.java b/opendj-sdk/opends/src/server/org/opends/server/types/SortKey.java
index 9d545bb..1087f6c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/SortKey.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/SortKey.java
@@ -280,5 +280,94 @@
 
     buffer.append(")");
   }
+
+  /**
+   * Retrieves the hash code for this sort key.
+   *
+   * @return  The hash code for this sort key.
+   */
+  public int hashCode()
+  {
+    int hashCode = 0;
+
+    if(ascending)
+    {
+      hashCode += 1;
+    }
+
+    hashCode += attributeType.hashCode();
+
+    if(orderingRule != null)
+    {
+      hashCode += orderingRule.hashCode();
+    }
+
+    return hashCode;
+  }
+
+  /**
+   * Indicates whether this sort key is equal to the provided
+   * object.
+   *
+   * @param  o  The object for which to make the determination.
+   *
+   * @return  <CODE>true</CODE> if the provide object is equal to this
+   *          sort key, or <CODE>false</CODE> if it is not.
+   */
+  public boolean equals(Object o)
+  {
+    if(o == null)
+    {
+      return false;
+    }
+
+    if (o == this)
+    {
+      return true;
+    }
+
+    if (! (o instanceof SortKey))
+    {
+      return false;
+    }
+
+    SortKey s = (SortKey) o;
+
+    if(ascending != s.ascending)
+    {
+      return false;
+    }
+
+    if(!attributeType.equals(s.attributeType))
+    {
+      return false;
+    }
+
+    if(orderingRule != null)
+    {
+      if(s.orderingRule != null)
+      {
+        if(!orderingRule.equals(s.orderingRule))
+        {
+          return false;
+        }
+      }
+      else if(!orderingRule.equals(
+          s.attributeType.getOrderingMatchingRule()))
+      {
+        return false;
+      }
+    }
+    else if(s.orderingRule != null)
+    {
+      if(!attributeType.getOrderingMatchingRule().equals(
+          s.orderingRule))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/SortOrder.java b/opendj-sdk/opends/src/server/org/opends/server/types/SortOrder.java
index d556bdc..419d91f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/SortOrder.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/SortOrder.java
@@ -131,5 +131,65 @@
 
     buffer.append(")");
   }
+
+  /**
+   * Retrieves the hash code for this sort order.
+   *
+   * @return  The hash code for this sort order.
+   */
+  public int hashCode()
+  {
+    int hashCode = 0;
+    for(SortKey sortKey : sortKeys)
+    {
+      hashCode += sortKey.hashCode();
+    }
+
+    return hashCode;
+  }
+
+  /**
+   * Indicates whether this sort order is equal to the provided
+   * object.
+   *
+   * @param  o  The object for which to make the determination.
+   *
+   * @return  <CODE>true</CODE> if the provide object is equal to this
+   *          sort order, or <CODE>false</CODE> if it is not.
+   */
+  public boolean equals(Object o)
+  {
+    if(o == null)
+    {
+      return false;
+    }
+
+    if (o == this)
+    {
+      return true;
+    }
+
+    if (! (o instanceof SortOrder))
+    {
+      return false;
+    }
+
+    SortOrder s = (SortOrder) o;
+
+    if(sortKeys.length != s.sortKeys.length)
+    {
+      return false;
+    }
+
+    for(int i = 0; i < sortKeys.length; i++)
+    {
+      if(!sortKeys[i].equals(s.sortKeys[i]))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
 }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
index b70ad9f..5f40d48 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -402,6 +402,22 @@
 ds-cfg-backend-entries-compressed: false
 ds-cfg-backend-deadlock-retry-limit: 10
 
+dn: cn=VLV Index,ds-cfg-backend-id=rebuildRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-branch
+cn: VLV Index
+
+dn: ds-cfg-vlv-je-index-name=testvlvindex,cn=VLV Index,ds-cfg-backend-id=rebuildRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-vlv-je-index
+ds-cfg-vlv-je-index-name: testvlvindex
+ds-cfg-vlv-je-index-base-dn: dc=rebuild, dc=jeb
+ds-cfg-vlv-je-index-scope: whole-subtree
+ds-cfg-vlv-je-index-filter: (objectClass=*)
+ds-cfg-vlv-je-index-sort-order: givenname -sn +uid
+
 dn: cn=Index,ds-cfg-backend-id=rebuildRoot,cn=Backends,cn=config
 changetype: add
 objectClass: top
@@ -443,6 +459,22 @@
 ds-cfg-backend-entries-compressed: false
 ds-cfg-backend-deadlock-retry-limit: 10
 
+dn: cn=VLV Index,ds-cfg-backend-id=importRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-branch
+cn: VLV Index
+
+dn: ds-cfg-vlv-je-index-name=testvlvindex,cn=VLV Index,ds-cfg-backend-id=importRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-vlv-je-index
+ds-cfg-vlv-je-index-name: testvlvindex
+ds-cfg-vlv-je-index-base-dn: dc=com
+ds-cfg-vlv-je-index-scope: whole-subtree
+ds-cfg-vlv-je-index-filter: (objectClass=*)
+ds-cfg-vlv-je-index-sort-order: givenname -sn +uid
+
 dn: ds-cfg-backend-id=verifyRoot,cn=Backends,cn=config
 changetype: add
 objectClass: top
@@ -466,6 +498,22 @@
 ds-cfg-backend-entries-compressed: false
 ds-cfg-backend-deadlock-retry-limit: 10
 
+dn: cn=VLV Index,ds-cfg-backend-id=verifyRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-branch
+cn: VLV Index
+
+dn: ds-cfg-vlv-je-index-name=testvlvindex,cn=VLV Index,ds-cfg-backend-id=verifyRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-vlv-je-index
+ds-cfg-vlv-je-index-name: testvlvindex
+ds-cfg-vlv-je-index-base-dn: dc=verify, dc=jeb
+ds-cfg-vlv-je-index-scope: whole-subtree
+ds-cfg-vlv-je-index-filter: (objectClass=*)
+ds-cfg-vlv-je-index-sort-order: givenname -sn +uid
+
 dn: cn=Index,ds-cfg-backend-id=verifyRoot,cn=Backends,cn=config
 changetype: add
 objectClass: top
@@ -557,6 +605,7 @@
 ds-cfg-backend-writability-mode: enabled
 ds-cfg-backend-base-dn: dc=test,dc=com
 ds-cfg-backend-base-dn: dc=test1,dc=com
+ds-cfg-backend-base-dn: dc=vlvtest,dc=com
 ds-cfg-backend-directory: db_index_test
 ds-cfg-backend-mode: 700
 ds-cfg-backend-index-entry-limit: 13
@@ -570,6 +619,23 @@
 ds-cfg-backend-entries-compressed: false
 ds-cfg-backend-deadlock-retry-limit: 10
 
+dn: cn=VLV Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-branch
+cn: VLV Index
+
+dn: ds-cfg-vlv-je-index-name=testvlvindex,cn=VLV Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
+changetype: add
+objectClass: top
+objectClass: ds-cfg-vlv-je-index
+ds-cfg-vlv-je-index-name: testvlvindex
+ds-cfg-vlv-je-index-base-dn: dc=vlvtest,dc=com
+ds-cfg-vlv-je-index-scope: whole-subtree
+ds-cfg-vlv-je-index-filter: (objectClass=*)
+ds-cfg-vlv-je-index-sort-order: givenname -sn +uid
+ds-cfg-vlv-je-index-maximum-block-size: 7
+
 dn: cn=Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
 changetype: add
 objectClass: top
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestRebuildJob.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestRebuildJob.java
index 90e16e8..5d57c94 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestRebuildJob.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestRebuildJob.java
@@ -73,7 +73,8 @@
         { "mail.substring" },
         { "mail.ordering" },
         { "mail.equality" },
-        { "mail.approximate" }
+        { "mail.approximate" },
+        { "vlv.testvlvindex" }
     };
   }
 
@@ -164,8 +165,14 @@
     be=(BackendImpl) DirectoryServer.getBackend(beID);
     be.rebuildBackend(rebuildConfig);
 
-    assertEquals(verifyBackend("mail"), 0);
-
+    if(index.contains(".") && !index.startsWith("vlv."))
+    {
+      assertEquals(verifyBackend(index.split("\\.")[0]), 0);
+    }
+    else
+    {
+      assertEquals(verifyBackend(index), 0);
+    }
   }
 
   @Test(dataProvider = "badIndexes",
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVLVIndex.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVLVIndex.java
new file mode 100644
index 0000000..a0f58bd
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVLVIndex.java
@@ -0,0 +1,1105 @@
+/*
+ * 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 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.jeb;
+
+import org.opends.server.TestCaseUtils;
+import static org.opends.server.util.ServerConstants.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL;
+import static org.opends.server.util.ServerConstants.OID_VLV_RESPONSE_CONTROL;
+import org.opends.server.controls.ServerSideSortRequestControl;
+import org.opends.server.controls.VLVRequestControl;
+import org.opends.server.controls.ServerSideSortResponseControl;
+import org.opends.server.controls.VLVResponseControl;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.types.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.annotations.AfterClass;
+import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+
+import java.util.*;
+
+public class TestVLVIndex
+{
+  SortOrder sortOrder;
+
+  private  String beID="indexRoot";
+  private BackendImpl be;
+
+  // 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;
+
+  // The DN for suffix
+  DN suffixDN;
+
+  TreeSet<SortValues> expectedSortedValues;
+
+  @BeforeClass
+  public void setUp() throws Exception {
+    TestCaseUtils.startServer();
+
+    SortKey[] sortKeys = new SortKey[3];
+    sortKeys[0] = new SortKey(DirectoryServer.getAttributeType("givenname"), true);
+    sortKeys[1] = new SortKey(DirectoryServer.getAttributeType("sn"),
+                              false);
+    sortKeys[2] = new SortKey(
+        DirectoryServer.getAttributeType("uid"), true);
+    sortOrder = new SortOrder(sortKeys);
+
+    aaccfJohnsonDN    = DN.decode("uid=aaccf.johnson,dc=vlvtest,dc=com");
+    aaronZimmermanDN  = DN.decode("uid=aaron.zimmerman,dc=vlvtest,dc=com");
+    albertSmithDN     = DN.decode("uid=albert.smith,dc=vlvtest,dc=com");
+    albertZimmermanDN = DN.decode("uid=albert.zimmerman,dc=vlvtest,dc=com");
+    lowercaseMcGeeDN  = DN.decode("uid=lowercase.mcgee,dc=vlvtest,dc=com");
+    margaretJonesDN   = DN.decode("uid=margaret.jones,dc=vlvtest,dc=com");
+    maryJonesDN       = DN.decode("uid=mary.jones,dc=vlvtest,dc=com");
+    samZweckDN        = DN.decode("uid=sam.zweck,dc=vlvtest,dc=com");
+    zorroDN           = DN.decode("uid=zorro,dc=vlvtest,dc=com");
+    suffixDN          = DN.decode("dc=vlvtest,dc=com");
+
+    expectedSortedValues = new TreeSet<SortValues>();
+  }
+
+  @AfterClass
+  public void cleanUp() throws Exception {
+  }
+
+  /**
+   * Populates the JE DB with a set of test data.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  private void populateDB()
+      throws Exception
+  {
+    List<Entry> entries = TestCaseUtils.makeEntries(
+        "dn: dc=vlvtest,dc=com",
+        "objectClass: top",
+        "objectClass: domain",
+        "",
+        "dn: uid=albert.zimmerman,dc=vlvtest,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=vlvtest,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=vlvtest,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=vlvtest,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=vlvtest,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=vlvtest,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=vlvtest,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=vlvtest,dc=com",
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "uid: lowercase.mcgee",
+        "givenName: lowercase",
+        "sn: mcgee",
+        "cn: lowercase mcgee",
+        "",
+        "dn: uid=zorro,dc=vlvtest,dc=com",
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "uid: zorro",
+        "sn: Zorro",
+        "cn: Zorro"
+    );
+
+    long id = 1;
+    for(Entry entry : entries)
+    {
+      TestCaseUtils.addEntry(entry);
+      expectedSortedValues.add(new SortValues(new EntryID(id), entry,
+                                              sortOrder));
+      id++;
+    }
+  }
+
+
+  @Test
+  public void testAdd() throws Exception
+  {
+    populateDB();
+    be=(BackendImpl) DirectoryServer.getBackend(beID);
+    RootContainer rootContainer = be.getRootContainer();
+    EntryContainer entryContainer =
+        rootContainer.getEntryContainer(DN.decode("dc=vlvtest,dc=com"));
+
+    for(VLVIndex vlvIndex : entryContainer.getVLVIndexes())
+    {
+      if(vlvIndex.getName().contains("testvlvindex"))
+      {
+
+
+        SortValuesSet svs1 =
+            vlvIndex.getSortValuesSet(null, 0,
+                                      expectedSortedValues.first().getValues());
+
+        assertNotNull(svs1);
+        assertEquals(svs1.size(), 4);
+
+        SortValuesSet svs2 =
+            vlvIndex.getSortValuesSet(null, 0,
+                                      expectedSortedValues.last().getValues());
+
+        assertNotNull(svs2);
+        assertEquals(svs2.size(), 6);
+
+        int i = 0;
+        for(SortValues values : expectedSortedValues)
+        {
+          for(int j = 0; j < values.getValues().length; j++)
+          {
+            byte[] value;
+            if(i < 4)
+            {
+              value = svs1.getValue(i * 3 + j);
+            }
+            else
+            {
+              value = svs2.getValue((i - 4) * 3 + j);
+            }
+            byte[] oValue = null;
+            if(values.getValues()[j] != null)
+            {
+              oValue = values.getValues()[j].getNormalizedValueBytes();
+            }
+            assertEquals(value, oValue);
+          }
+          i++;
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an offset of one.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetOneOffset()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, 1, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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, bigger
+    expectedDNOrder.add(albertSmithDN);     // Albert, smaller sn
+
+    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(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * 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( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetZeroOffset()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, 0, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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, bigger
+    expectedDNOrder.add(albertSmithDN);     // Albert, smaller sn
+
+    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(), 10);
+  }
+
+  /**
+   * 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( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetThreeOffset()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, 3, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
+
+    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
+    expectedDNOrder.add(albertZimmermanDN); // Albert, bigger
+    expectedDNOrder.add(albertSmithDN);     // Albert, smaller sn
+    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
+    expectedDNOrder.add(margaretJonesDN);   // Maggie
+
+    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(), 3);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control with a negative
+   * target offset.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetNegativeOffset()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, -1, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetNegativeStartPosition()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(3, 3, 1, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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.SUCCESS);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control with a start
+   * start position beyond the end of the result set.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetStartPositionTooHigh()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(3, 3, 30, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+
+    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
+
+    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
+    expectedDNOrder.add(samZweckDN);        // Sam
+    expectedDNOrder.add(zorroDN);           // No first name
+    expectedDNOrder.add(suffixDN);          // No sort attributes
+
+    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);
+
+    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.SUCCESS);
+    assertEquals(vlvResponse.getTargetPosition(), 11);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control with a start
+   * start position within the bounds of the list but not enough remaining
+   * entries to meet the afterCount
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByOffsetIncompleteAfterCount()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 4, 7, 0));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+    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
+    expectedDNOrder.add(suffixDN);          // No sort attributes
+
+    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(), 7);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an assertion value before any actual value in
+   * the list.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByValueBeforeAll()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, new ASN1OctetString("a")));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an assertion value that matches the first value
+   * in the list.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByValueMatchesFirst()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3,
+                                              new ASN1OctetString("aaccf")));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    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(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an assertion value that matches the third value
+   * in the list.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByValueMatchesThird()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3,
+                                              new ASN1OctetString("albert")));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
+
+    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
+    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
+    expectedDNOrder.add(albertSmithDN);     // Albert, higher entry ID
+    expectedDNOrder.add(lowercaseMcGeeDN);  // lowercase
+    expectedDNOrder.add(margaretJonesDN);   // Maggie
+
+    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(), 3);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an assertion value that matches the third value
+   * in the list and includes a nonzero before count.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByValueMatchesThirdWithBeforeCount()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(1, 3,
+                                              new ASN1OctetString("albert")));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
+
+    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
+    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
+
+    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(), 3);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+  /**
+   * Tests performing an internal search using the VLV control to retrieve a
+   * subset of the entries using an assertion value that is after all values in
+   * the list.
+   *
+   * @throws  Exception  If an unexpected problem occurred.
+   */
+  @Test( dependsOnMethods = { "testAdd" } )
+  public void testInternalSearchByValueAfterAll()
+      throws Exception
+  {
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+
+    ArrayList<Control> requestControls = new ArrayList<Control>();
+    requestControls.add(new ServerSideSortRequestControl(sortOrder));
+    requestControls.add(new VLVRequestControl(0, 3, new ASN1OctetString("zz")));
+
+    InternalSearchOperation internalSearch =
+        new InternalSearchOperation(conn, conn.nextOperationID(),
+                                    conn.nextMessageID(), requestControls,
+                                    DN.decode("dc=vlvtest,dc=com"), SearchScope.WHOLE_SUBTREE,
+                                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                                    SearchFilter.createFilterFromString("(objectClass=*)"),
+                                    null, null);
+
+    internalSearch.run();
+
+    // It will be successful because the control isn't critical.
+    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
+
+    // Null values for given name are still bigger then zz
+    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
+    expectedDNOrder.add(zorroDN);           // No first name
+    expectedDNOrder.add(suffixDN);          // No sort attributes
+
+    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);
+
+    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.SUCCESS);
+    assertEquals(vlvResponse.getTargetPosition(), 9);
+    assertEquals(vlvResponse.getContentCount(), 10);
+  }
+
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
index fe22ecd..6b57090 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
@@ -373,6 +373,53 @@
     }
   }
 
+  /**
+   * Runs clean verify against the testvlvindex VLV index
+   * after adding various errors to each of these index files.
+   * @throws Exception if the error count is not equal to 6.
+   */
+  @Test() public void testCleanVLV() throws Exception {
+    String indexName = "testvlvindex";
+    preTest(4);
+    eContainer.sharedLock.lock();
+    try
+    {
+      VLVIndex vlvIndex = eContainer.getVLVIndex(indexName);
+
+      // Add an incorrectly ordered values.
+      SortValuesSet svs =
+          vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]);
+      long id = svs.getEntryIDs()[0];
+      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id));
+
+      SortValuesSet svs2 = svs.split(2);
+      svs2.add(id, vlvIndex.getSortValues(entry));
+
+      // Add an invalid ID
+      svs2.add(1000, vlvIndex.getSortValues(entry));
+
+      // Muck up the values of another ID
+      id = svs.getEntryIDs()[0];
+      entry = eContainer.getID2Entry().get(null, new EntryID(id));
+      AttributeValue[] values = vlvIndex.getSortValues(entry);
+      AttributeValue[] badValues = new AttributeValue[values.length];
+      System.arraycopy(values, 1, badValues, 0, values.length - 1);
+      badValues[badValues.length-1] = values[0];
+      svs.remove(id, values);
+      svs.add(id, badValues);
+
+      vlvIndex.putSortValuesSet(null, svs);
+      vlvIndex.putSortValuesSet(null, svs2);
+      performBECleanVerify("vlv." + indexName, 3);
+    }
+    finally
+    {
+      eContainer.sharedLock.unlock();
+    }
+
+  }
+
+
   /*
      * Begin complete verify index tests. As described above, these are
      * tests that cursor through the id2entry database and validate
@@ -631,6 +678,51 @@
     }
   }
 
+  /**
+   * Runs complete verify against the testvlvindex VLV index
+   * after adding various errors to each of these index files.
+   * @throws Exception if the error count is not equal to 6.
+   */
+  @Test() public void testVerifyVLV() throws Exception {
+    String indexName = "testvlvindex";
+    preTest(4);
+    eContainer.sharedLock.lock();
+    try
+    {
+      VLVIndex vlvIndex = eContainer.getVLVIndex(indexName);
+
+      // Remove an ID
+      SortValuesSet svs =
+          vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]);
+      long id = svs.getEntryIDs()[0];
+      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id));
+      svs.remove(id, vlvIndex.getSortValues(entry));
+
+      // Add an incorrectly ordered values.
+      SortValuesSet svs2 = svs.split(2);
+      svs2.add(1000, vlvIndex.getSortValues(entry));
+
+      // Muck up the values of another ID
+      id = svs.getEntryIDs()[0];
+      entry = eContainer.getID2Entry().get(null, new EntryID(id));
+      AttributeValue[] values = vlvIndex.getSortValues(entry);
+      AttributeValue[] badValues = new AttributeValue[values.length];
+      System.arraycopy(values, 1, badValues, 0, values.length - 1);
+      badValues[badValues.length-1] = values[0];
+      svs.remove(id, values);
+      svs.add(id, badValues);
+
+      vlvIndex.putSortValuesSet(null, svs);
+      vlvIndex.putSortValuesSet(null, svs2);
+      performBECompleteVerify("vlv." + indexName, 3);
+    }
+    finally
+    {
+      eContainer.sharedLock.unlock();
+    }
+
+  }
+
   /* Various tests not either clean or complete */
 
 

--
Gitblit v1.10.0