001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS
026 */
027package org.opends.server.backends.jeb;
028
029import static com.sleepycat.je.OperationStatus.*;
030
031import static org.opends.messages.BackendMessages.*;
032
033import java.util.*;
034
035import org.forgerock.i18n.slf4j.LocalizedLogger;
036import org.forgerock.opendj.ldap.ByteString;
037import org.forgerock.opendj.ldap.ConditionResult;
038import org.opends.server.backends.jeb.IndexBuffer.BufferedIndexValues;
039import org.opends.server.types.DirectoryException;
040import org.opends.server.types.Entry;
041import org.opends.server.types.Modification;
042import org.opends.server.util.StaticUtils;
043
044import com.sleepycat.je.*;
045
046/**
047 * Represents an index implemented by a JE database in which each key maps to
048 * a set of entry IDs.  The key is a byte array, and is constructed from some
049 * normalized form of an attribute value (or fragment of a value) appearing
050 * in the entry.
051 */
052public class Index extends DatabaseContainer
053{
054  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
055
056  /** The indexer object to construct index keys from LDAP attribute values. */
057  private Indexer indexer;
058
059  /** The limit on the number of entry IDs that may be indexed by one key. */
060  private int indexEntryLimit;
061  /**
062   * Limit on the number of entry IDs that may be retrieved by cursoring
063   * through an index.
064   */
065  private final int cursorEntryLimit;
066  /**
067   * Number of keys that have exceeded the entry limit since this
068   * object was created.
069   */
070  private int entryLimitExceededCount;
071
072  /** The max number of tries to rewrite phantom records. */
073  private final int phantomWriteRetries = 3;
074
075  /**
076   * Whether to maintain a count of IDs for a key once the entry limit
077   * has exceeded.
078   */
079  private final boolean maintainCount;
080
081  private final State state;
082
083  /**
084   * A flag to indicate if this index should be trusted to be consistent
085   * with the entries database. If not trusted, we assume that existing
086   * entryIDSets for a key is still accurate. However, keys that do not
087   * exist are undefined instead of an empty entryIDSet. The following
088   * rules will be observed when the index is not trusted:
089   *
090   * - no entryIDs will be added to a non-existing key.
091   * - undefined entryIdSet will be returned whenever a key is not found.
092   */
093  private boolean trusted;
094
095  private final ImportIDSet newImportIDSet;
096
097  /**
098   * Create a new index object.
099   * @param name The name of the index database within the entryContainer.
100   * @param indexer The indexer object to construct index keys from LDAP
101   * attribute values.
102   * @param state The state database to persist index state info.
103   * @param indexEntryLimit The configured limit on the number of entry IDs
104   * that may be indexed by one key.
105   * @param cursorEntryLimit The configured limit on the number of entry IDs
106   * @param maintainCount Whether to maintain a count of IDs for a key once
107   * the entry limit has exceeded.
108   * @param env The JE Environment
109   * @param entryContainer The database entryContainer holding this index.
110   * @throws DatabaseException If an error occurs in the JE database.
111   */
112  @SuppressWarnings("unchecked")
113  Index(String name, Indexer indexer, State state,
114        int indexEntryLimit, int cursorEntryLimit, boolean maintainCount,
115        Environment env, EntryContainer entryContainer)
116      throws DatabaseException
117  {
118    super(name, env, entryContainer);
119    this.indexer = indexer;
120    this.indexEntryLimit = indexEntryLimit;
121    this.cursorEntryLimit = cursorEntryLimit;
122    this.maintainCount = maintainCount;
123    this.newImportIDSet = new ImportIDSet(indexEntryLimit,
124                                          indexEntryLimit, maintainCount);
125
126    this.dbConfig = JEBUtils.toDatabaseConfigNoDuplicates(env);
127    this.dbConfig.setOverrideBtreeComparator(true);
128    this.dbConfig.setBtreeComparator((Class<? extends Comparator<byte[]>>)
129                                     indexer.getComparator().getClass());
130
131    this.state = state;
132
133    this.trusted = state.getIndexTrustState(null, this);
134    if (!trusted && entryContainer.getHighestEntryID().longValue() == 0)
135    {
136      // If there are no entries in the entry container then there
137      // is no reason why this index can't be upgraded to trusted.
138      setTrusted(null, true);
139    }
140  }
141
142  void indexEntry(Entry entry, Set<ByteString> keys)
143  {
144    indexer.indexEntry(entry, keys);
145  }
146
147  /**
148   * Add an add entry ID operation into a index buffer.
149   *
150   * @param buffer The index buffer to insert the ID into.
151   * @param keyBytes         The index key bytes.
152   * @param entryID     The entry ID.
153   */
154  void insertID(IndexBuffer buffer, ByteString keyBytes, EntryID entryID)
155  {
156    getBufferedIndexValues(buffer, keyBytes).addEntryID(keyBytes, entryID);
157  }
158
159  /**
160   * Delete the specified import ID set from the import ID set associated with the key.
161   *
162   * @param key The key to delete the set from.
163   * @param importIdSet The import ID set to delete.
164   * @param data A database entry to use for data.
165   * @throws DatabaseException If a database error occurs.
166   */
167  public void delete(DatabaseEntry key, ImportIDSet importIdSet, DatabaseEntry data) throws DatabaseException {
168    if (read(null, key, data, LockMode.DEFAULT) == SUCCESS) {
169      newImportIDSet.clear();
170      newImportIDSet.remove(data.getData(), importIdSet);
171      if (newImportIDSet.isDefined() && newImportIDSet.size() == 0)
172      {
173        delete(null, key);
174      }
175      else
176      {
177        data.setData(newImportIDSet.toDatabase());
178        put(null, key, data);
179      }
180    } else {
181      // Should never happen -- the keys should always be there.
182      throw new RuntimeException();
183    }
184  }
185
186  /**
187   * Insert the specified import ID set into this index. Creates a DB cursor if needed.
188   *
189   * @param key The key to add the set to.
190   * @param importIdSet The set of import IDs.
191   * @param data Database entry to reuse for read.
192   * @throws DatabaseException If a database error occurs.
193   */
194  public void insert(DatabaseEntry key, ImportIDSet importIdSet, DatabaseEntry data) throws DatabaseException {
195    final OperationStatus status = read(null, key, data, LockMode.DEFAULT);
196    if(status == OperationStatus.SUCCESS) {
197      newImportIDSet.clear();
198      if (newImportIDSet.merge(data.getData(), importIdSet))
199      {
200        entryLimitExceededCount++;
201      }
202      data.setData(newImportIDSet.toDatabase());
203      put(null, key, data);
204    } else if(status == OperationStatus.NOTFOUND) {
205      if(!importIdSet.isDefined()) {
206        entryLimitExceededCount++;
207      }
208      data.setData(importIdSet.toDatabase());
209      put(null, key, data);
210    } else {
211      // Should never happen during import.
212      throw new RuntimeException();
213    }
214  }
215
216  /**
217   * Update the set of entry IDs for a given key.
218   *
219   * @param txn A database transaction, or null if none is required.
220   * @param key The database key.
221   * @param deletedIDs The IDs to remove for the key.
222   * @param addedIDs the IDs to add for the key.
223   * @throws DatabaseException If a database error occurs.
224   */
225  void updateKey(Transaction txn, DatabaseEntry key, EntryIDSet deletedIDs, EntryIDSet addedIDs)
226      throws DatabaseException
227  {
228    DatabaseEntry data = new DatabaseEntry();
229
230    if(deletedIDs == null && addedIDs == null)
231    {
232      final OperationStatus status = delete(txn, key);
233      if (status != SUCCESS && logger.isTraceEnabled())
234      {
235        StringBuilder builder = new StringBuilder();
236        StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
237        logger.trace("The expected key does not exist in the index %s.\nKey:%s ", name, builder);
238      }
239      return;
240    }
241
242    // Handle cases where nothing is changed early to avoid DB access.
243    if (isNullOrEmpty(deletedIDs) && isNullOrEmpty(addedIDs))
244    {
245      return;
246    }
247
248    if(maintainCount)
249    {
250      for (int i = 0; i < phantomWriteRetries; i++)
251      {
252        if (updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == SUCCESS)
253        {
254          return;
255        }
256      }
257    }
258    else
259    {
260      OperationStatus status = read(txn, key, data, LockMode.READ_COMMITTED);
261      if(status == OperationStatus.SUCCESS)
262      {
263        EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData());
264        if (entryIDList.isDefined())
265        {
266          for (int i = 0; i < phantomWriteRetries; i++)
267          {
268            if (updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == SUCCESS)
269            {
270              return;
271            }
272          }
273        }
274      }
275      else if (trusted)
276      {
277        if (deletedIDs != null)
278        {
279          logIndexCorruptError(txn, key);
280        }
281
282        if (isNotNullOrEmpty(addedIDs))
283        {
284          data.setData(addedIDs.toDatabase());
285
286          status = insert(txn, key, data);
287          if(status == OperationStatus.KEYEXIST)
288          {
289            for (int i = 1; i < phantomWriteRetries; i++)
290            {
291              if (updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == SUCCESS)
292              {
293                return;
294              }
295            }
296          }
297        }
298      }
299    }
300  }
301
302  private boolean isNullOrEmpty(EntryIDSet entryIDSet)
303  {
304    return entryIDSet == null || entryIDSet.size() == 0;
305  }
306
307  private boolean isNotNullOrEmpty(EntryIDSet entryIDSet)
308  {
309    return entryIDSet != null && entryIDSet.size() > 0;
310  }
311
312  private OperationStatus updateKeyWithRMW(Transaction txn,
313                                           DatabaseEntry key,
314                                           DatabaseEntry data,
315                                           EntryIDSet deletedIDs,
316                                           EntryIDSet addedIDs)
317      throws DatabaseException
318  {
319    final OperationStatus status = read(txn, key, data, LockMode.RMW);
320    if(status == SUCCESS)
321    {
322      EntryIDSet entryIDList = computeEntryIDList(key, data, deletedIDs, addedIDs);
323      byte[] after = entryIDList.toDatabase();
324      if (after != null)
325      {
326        data.setData(after);
327        return put(txn, key, data);
328      }
329      else
330      {
331        // No more IDs, so remove the key. If index is not
332        // trusted then this will cause all subsequent reads
333        // for this key to return undefined set.
334        return delete(txn, key);
335      }
336    }
337    else if (trusted)
338    {
339      if (deletedIDs != null)
340      {
341        logIndexCorruptError(txn, key);
342      }
343
344      if (isNotNullOrEmpty(addedIDs))
345      {
346        data.setData(addedIDs.toDatabase());
347        return insert(txn, key, data);
348      }
349    }
350    return OperationStatus.SUCCESS;
351  }
352
353  private EntryIDSet computeEntryIDList(DatabaseEntry key, DatabaseEntry data, EntryIDSet deletedIDs,
354      EntryIDSet addedIDs)
355  {
356    EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData());
357    if(addedIDs != null)
358    {
359      if(entryIDList.isDefined() && indexEntryLimit > 0)
360      {
361        long idCountDelta = addedIDs.size();
362        if(deletedIDs != null)
363        {
364          idCountDelta -= deletedIDs.size();
365        }
366        if(idCountDelta + entryIDList.size() >= indexEntryLimit)
367        {
368          if(maintainCount)
369          {
370            entryIDList = new EntryIDSet(entryIDList.size() + idCountDelta);
371          }
372          else
373          {
374            entryIDList = new EntryIDSet();
375          }
376          entryLimitExceededCount++;
377
378          if(logger.isTraceEnabled())
379          {
380            StringBuilder builder = new StringBuilder();
381            StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
382            logger.trace("Index entry exceeded in index %s. " +
383                "Limit: %d. ID list size: %d.\nKey:%s",
384                name, indexEntryLimit, idCountDelta + addedIDs.size(), builder);
385
386          }
387        }
388        else
389        {
390          entryIDList.addAll(addedIDs);
391          if(deletedIDs != null)
392          {
393            entryIDList.deleteAll(deletedIDs);
394          }
395        }
396      }
397      else
398      {
399        entryIDList.addAll(addedIDs);
400        if(deletedIDs != null)
401        {
402          entryIDList.deleteAll(deletedIDs);
403        }
404      }
405    }
406    else if(deletedIDs != null)
407    {
408      entryIDList.deleteAll(deletedIDs);
409    }
410    return entryIDList;
411  }
412
413  /**
414   * Add an remove entry ID operation into a index buffer.
415   *
416   * @param buffer The index buffer to insert the ID into.
417   * @param keyBytes    The index key bytes.
418   * @param entryID     The entry ID.
419   */
420  void removeID(IndexBuffer buffer, ByteString keyBytes, EntryID entryID)
421  {
422    getBufferedIndexValues(buffer, keyBytes).deleteEntryID(keyBytes, entryID);
423  }
424
425  private void logIndexCorruptError(Transaction txn, DatabaseEntry key)
426  {
427    if (logger.isTraceEnabled())
428    {
429      StringBuilder builder = new StringBuilder();
430      StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
431      logger.trace("The expected key does not exist in the index %s.\nKey:%s", name, builder);
432    }
433
434    setTrusted(txn, false);
435    logger.error(ERR_INDEX_CORRUPT_REQUIRES_REBUILD, name);
436  }
437
438  /**
439   * Buffered delete of a key from the JE database.
440   * @param buffer The index buffer to use to store the deleted keys
441   * @param keyBytes The index key bytes.
442   */
443  public void delete(IndexBuffer buffer, ByteString keyBytes)
444  {
445    getBufferedIndexValues(buffer, keyBytes);
446  }
447
448  private BufferedIndexValues getBufferedIndexValues(IndexBuffer buffer, ByteString keyBytes)
449  {
450    return buffer.getBufferedIndexValues(this, keyBytes, indexer.getBSComparator());
451  }
452
453  /**
454   * Check if an entry ID is in the set of IDs indexed by a given key.
455   *
456   * @param txn A database transaction, or null if none is required.
457   * @param key         The index key.
458   * @param entryID     The entry ID.
459   * @return true if the entry ID is indexed by the given key,
460   *         false if it is not indexed by the given key,
461   *         undefined if the key has exceeded the entry limit.
462   * @throws DatabaseException If an error occurs in the JE database.
463   */
464  public ConditionResult containsID(Transaction txn, DatabaseEntry key, EntryID entryID)
465       throws DatabaseException
466  {
467    DatabaseEntry data = new DatabaseEntry();
468
469    OperationStatus status = read(txn, key, data, LockMode.DEFAULT);
470    if (status == SUCCESS)
471    {
472      EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData());
473      if (!entryIDList.isDefined())
474      {
475        return ConditionResult.UNDEFINED;
476      }
477      return ConditionResult.valueOf(entryIDList.contains(entryID));
478    }
479    else if (trusted)
480    {
481      return ConditionResult.FALSE;
482    }
483    else
484    {
485      return ConditionResult.UNDEFINED;
486    }
487  }
488
489  /**
490   * Reads the set of entry IDs for a given key.
491   *
492   * @param key The database key.
493   * @param txn A database transaction, or null if none is required.
494   * @param lockMode The JE locking mode to be used for the database read.
495   * @return The entry IDs indexed by this key.
496   */
497  public EntryIDSet readKey(DatabaseEntry key, Transaction txn, LockMode lockMode)
498  {
499    try
500    {
501      DatabaseEntry data = new DatabaseEntry();
502      OperationStatus status = read( txn, key, data, lockMode);
503      if (status != SUCCESS)
504      {
505        if(trusted)
506        {
507          return new EntryIDSet(key.getData(), null);
508        }
509        else
510        {
511          return new EntryIDSet();
512        }
513      }
514      return new EntryIDSet(key.getData(), data.getData());
515    }
516    catch (DatabaseException e)
517    {
518      logger.traceException(e);
519      return new EntryIDSet();
520    }
521  }
522
523  /**
524   * Writes the set of entry IDs for a given key.
525   *
526   * @param key The database key.
527   * @param entryIDList The entry IDs indexed by this key.
528   * @param txn A database transaction, or null if none is required.
529   * @throws DatabaseException If an error occurs in the JE database.
530   */
531  public void writeKey(Transaction txn, DatabaseEntry key, EntryIDSet entryIDList)
532       throws DatabaseException
533  {
534    DatabaseEntry data = new DatabaseEntry();
535    byte[] after = entryIDList.toDatabase();
536    if (after != null)
537    {
538      if (!entryIDList.isDefined())
539      {
540        entryLimitExceededCount++;
541      }
542      data.setData(after);
543      put(txn, key, data);
544    }
545    else
546    {
547      // No more IDs, so remove the key.
548      delete(txn, key);
549    }
550  }
551
552  /**
553   * Reads a range of keys and collects all their entry IDs into a
554   * single set.
555   *
556   * @param lower The lower bound of the range. A 0 length byte array indicates
557   *                      no lower bound and the range will start from the
558   *                      smallest key.
559   * @param upper The upper bound of the range. A 0 length byte array indicates
560   *                      no upper bound and the range will end at the largest
561   *                      key.
562   * @param lowerIncluded true if a key exactly matching the lower bound
563   *                      is included in the range, false if only keys
564   *                      strictly greater than the lower bound are included.
565   *                      This value is ignored if the lower bound is not
566   *                      specified.
567   * @param upperIncluded true if a key exactly matching the upper bound
568   *                      is included in the range, false if only keys
569   *                      strictly less than the upper bound are included.
570   *                      This value is ignored if the upper bound is not
571   *                      specified.
572   * @return The set of entry IDs.
573   */
574  public EntryIDSet readRange(byte[] lower, byte[] upper,
575                               boolean lowerIncluded, boolean upperIncluded)
576  {
577    // If this index is not trusted, then just return an undefined id set.
578    if (!trusted)
579    {
580      return new EntryIDSet();
581    }
582
583    try
584    {
585      // Total number of IDs found so far.
586      int totalIDCount = 0;
587      LockMode lockMode = LockMode.DEFAULT;
588
589      DatabaseEntry data = new DatabaseEntry();
590      DatabaseEntry key;
591
592      ArrayList<EntryIDSet> lists = new ArrayList<>();
593
594      Cursor cursor = openCursor(null, CursorConfig.READ_COMMITTED);
595      try
596      {
597        final Comparator<byte[]> comparator = indexer.getComparator();
598        OperationStatus status;
599        // Set the lower bound if necessary.
600        if(lower.length > 0)
601        {
602          key = new DatabaseEntry(lower);
603
604          // Initialize the cursor to the lower bound.
605          status = cursor.getSearchKeyRange(key, data, lockMode);
606
607          // Advance past the lower bound if necessary.
608          if (status == SUCCESS && !lowerIncluded &&
609               comparator.compare(key.getData(), lower) == 0)
610          {
611            // Do not include the lower value.
612            status = cursor.getNext(key, data, lockMode);
613          }
614        }
615        else
616        {
617          key = new DatabaseEntry();
618          status = cursor.getNext(key, data, lockMode);
619        }
620
621        if (status != OperationStatus.SUCCESS)
622        {
623          // There are no values.
624          return new EntryIDSet(key.getData(), null);
625        }
626
627        // Step through the keys until we hit the upper bound or the last key.
628        while (status == OperationStatus.SUCCESS)
629        {
630          // Check against the upper bound if necessary
631          if(upper.length > 0)
632          {
633            int cmp = comparator.compare(key.getData(), upper);
634            if (cmp > 0 || (cmp == 0 && !upperIncluded))
635            {
636              break;
637            }
638          }
639          EntryIDSet list = new EntryIDSet(key.getData(), data.getData());
640          if (!list.isDefined())
641          {
642            // There is no point continuing.
643            return list;
644          }
645          totalIDCount += list.size();
646          if (cursorEntryLimit > 0 && totalIDCount > cursorEntryLimit)
647          {
648            // There are too many. Give up and return an undefined list.
649            return new EntryIDSet();
650          }
651          lists.add(list);
652          status = cursor.getNext(key, data, LockMode.DEFAULT);
653        }
654
655        return EntryIDSet.unionOfSets(lists, false);
656      }
657      finally
658      {
659        cursor.close();
660      }
661    }
662    catch (DatabaseException e)
663    {
664      logger.traceException(e);
665      return new EntryIDSet();
666    }
667  }
668
669  /**
670   * Get the number of keys that have exceeded the entry limit since this
671   * object was created.
672   * @return The number of keys that have exceeded the entry limit since this
673   * object was created.
674   */
675  public int getEntryLimitExceededCount()
676  {
677    return entryLimitExceededCount;
678  }
679
680  /**
681   * Update the index buffer for a deleted entry.
682   *
683   * @param buffer The index buffer to use to store the deleted keys
684   * @param entryID     The entry ID.
685   * @param entry       The entry to be indexed.
686   * @throws DatabaseException If an error occurs in the JE database.
687   * @throws DirectoryException If a Directory Server error occurs.
688   */
689  public void addEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DatabaseException, DirectoryException
690  {
691    final Set<ByteString> addKeys = new HashSet<>();
692    indexer.indexEntry(entry, addKeys);
693
694    for (ByteString keyBytes : addKeys)
695    {
696      insertID(buffer, keyBytes, entryID);
697    }
698  }
699
700  /**
701   * Update the index buffer for a deleted entry.
702   *
703   * @param buffer The index buffer to use to store the deleted keys
704   * @param entryID     The entry ID
705   * @param entry       The contents of the deleted entry.
706   * @throws DatabaseException If an error occurs in the JE database.
707   * @throws DirectoryException If a Directory Server error occurs.
708   */
709  public void removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry)
710      throws DatabaseException, DirectoryException
711  {
712    final Set<ByteString> delKeys = new HashSet<>();
713    indexer.indexEntry(entry, delKeys);
714
715    for (ByteString keyBytes : delKeys)
716    {
717      removeID(buffer, keyBytes, entryID);
718    }
719  }
720
721  /**
722   * Update the index to reflect a sequence of modifications in a Modify
723   * operation.
724   *
725   * @param buffer The index buffer to use to store the deleted keys
726   * @param entryID The ID of the entry that was modified.
727   * @param oldEntry The entry before the modifications were applied.
728   * @param newEntry The entry after the modifications were applied.
729   * @param mods The sequence of modifications in the Modify operation.
730   * @throws DatabaseException If an error occurs in the JE database.
731   */
732  public void modifyEntry(IndexBuffer buffer,
733                          EntryID entryID,
734                          Entry oldEntry,
735                          Entry newEntry,
736                          List<Modification> mods)
737      throws DatabaseException
738  {
739    final Map<ByteString, Boolean> modifiedKeys = new TreeMap<>(indexer.getBSComparator());
740    indexer.modifyEntry(oldEntry, newEntry, mods, modifiedKeys);
741
742    for (Map.Entry<ByteString, Boolean> modifiedKey : modifiedKeys.entrySet())
743    {
744      if(modifiedKey.getValue())
745      {
746        insertID(buffer, modifiedKey.getKey(), entryID);
747      }
748      else
749      {
750        removeID(buffer, modifiedKey.getKey(), entryID);
751      }
752    }
753  }
754
755  /**
756   * Set the index entry limit.
757   *
758   * @param indexEntryLimit The index entry limit to set.
759   * @return True if a rebuild is required or false otherwise.
760   */
761  public boolean setIndexEntryLimit(int indexEntryLimit)
762  {
763    final boolean rebuildRequired =
764        this.indexEntryLimit < indexEntryLimit && entryLimitExceededCount > 0;
765    this.indexEntryLimit = indexEntryLimit;
766    return rebuildRequired;
767  }
768
769  /**
770   * Set the indexer.
771   *
772   * @param indexer The indexer to set
773   */
774  public void setIndexer(Indexer indexer)
775  {
776    this.indexer = indexer;
777  }
778
779  /**
780   * Return entry limit.
781   *
782   * @return The entry limit.
783   */
784  public int getIndexEntryLimit() {
785    return this.indexEntryLimit;
786  }
787
788  /**
789   * Set the index trust state.
790   * @param txn A database transaction, or null if none is required.
791   * @param trusted True if this index should be trusted or false
792   *                otherwise.
793   * @throws DatabaseException If an error occurs in the JE database.
794   */
795  public synchronized void setTrusted(Transaction txn, boolean trusted)
796      throws DatabaseException
797  {
798    this.trusted = trusted;
799    state.putIndexTrustState(txn, this, trusted);
800  }
801
802  /**
803   * Return true iff this index is trusted.
804   * @return the trusted state of this index
805   */
806  public synchronized boolean isTrusted()
807  {
808    return trusted;
809  }
810
811  /**
812   * Return <code>true</code> iff this index is being rebuilt.
813   * @return The rebuild state of this index
814   */
815  public synchronized boolean isRebuildRunning()
816  {
817    return false; // FIXME inline?
818  }
819
820  /**
821   * Whether this index maintains a count of IDs for keys once the
822   * entry limit has exceeded.
823   * @return <code>true</code> if this index maintains court of IDs
824   * or <code>false</code> otherwise
825   */
826  public boolean getMaintainCount()
827  {
828    return maintainCount;
829  }
830
831  /**
832   * Return an indexes comparator.
833   *
834   * @return The comparator related to an index.
835   */
836  public Comparator<byte[]> getComparator()
837  {
838    return indexer.getComparator();
839  }
840}