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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.backends.jeb;
028    import org.opends.messages.Message;
029    
030    import java.util.*;
031    
032    import com.sleepycat.je.*;
033    
034    import org.opends.server.api.SubstringMatchingRule;
035    import org.opends.server.api.OrderingMatchingRule;
036    import org.opends.server.api.ApproximateMatchingRule;
037    import org.opends.server.protocols.asn1.ASN1OctetString;
038    
039    import static org.opends.server.loggers.debug.DebugLogger.*;
040    import org.opends.server.loggers.debug.DebugTracer;
041    import org.opends.server.types.*;
042    import org.opends.server.admin.std.server.LocalDBIndexCfg;
043    import org.opends.server.admin.std.meta.LocalDBIndexCfgDefn;
044    import org.opends.server.admin.server.ConfigurationChangeListener;
045    import org.opends.server.config.ConfigException;
046    import static org.opends.messages.JebMessages.*;
047    
048    import org.opends.server.core.DirectoryServer;
049    import org.opends.server.util.StaticUtils;
050    
051    /**
052     * Class representing an attribute index.
053     * We have a separate database for each type of indexing, which makes it easy
054     * to tell which attribute indexes are configured.  The different types of
055     * indexing are equality, presence, substrings and ordering.  The keys in the
056     * ordering index are ordered by setting the btree comparator to the ordering
057     * matching rule comparator.
058     * Note that the values in the equality index are normalized by the equality
059     * matching rule, whereas the values in the ordering index are normalized
060     * by the ordering matching rule.  If these could be guaranteed to be identical
061     * then we would not need a separate ordering index.
062     */
063    public class AttributeIndex
064        implements ConfigurationChangeListener<LocalDBIndexCfg>
065    {
066      /**
067       * The tracer object for the debug logger.
068       */
069      private static final DebugTracer TRACER = getTracer();
070    
071    
072    
073      /**
074       * A database key for the presence index.
075       */
076      public static final DatabaseEntry presenceKey =
077           new DatabaseEntry("+".getBytes());
078    
079      /**
080       * The entryContainer in which this attribute index resides.
081       */
082      private EntryContainer entryContainer;
083    
084      private Environment env;
085    
086      /**
087       * The attribute index configuration.
088       */
089      private LocalDBIndexCfg indexConfig;
090    
091      /**
092       * The index database for attribute equality.
093       */
094      Index equalityIndex = null;
095    
096      /**
097       * The index database for attribute presence.
098       */
099      Index presenceIndex = null;
100    
101      /**
102       * The index database for attribute substrings.
103       */
104      Index substringIndex = null;
105    
106      /**
107       * The index database for attribute ordering.
108       */
109      Index orderingIndex = null;
110    
111      /**
112       * The index database for attribute approximate.
113       */
114      Index approximateIndex = null;
115    
116      private State state;
117    
118      private int cursorEntryLimit = 100000;
119    
120      /**
121       * Create a new attribute index object.
122       * @param entryContainer The entryContainer of this attribute index.
123       * @param state The state database to persist index state info.
124       * @param env The JE environment handle.
125       * @param indexConfig The attribute index configuration.
126       * @throws DatabaseException if a JE database error occurs.
127       * @throws ConfigException if a configuration related error occurs.
128       */
129      public AttributeIndex(LocalDBIndexCfg indexConfig, State state,
130                            Environment env,
131                            EntryContainer entryContainer)
132          throws DatabaseException, ConfigException
133      {
134        this.entryContainer = entryContainer;
135        this.env = env;
136        this.indexConfig = indexConfig;
137        this.state = state;
138    
139        AttributeType attrType = indexConfig.getAttribute();
140        String name =
141            entryContainer.getDatabasePrefix() + "_" + attrType.getNameOrOID();
142        int indexEntryLimit = indexConfig.getIndexEntryLimit();
143    
144        if (indexConfig.getIndexType().contains(
145                LocalDBIndexCfgDefn.IndexType.EQUALITY))
146        {
147          if (attrType.getEqualityMatchingRule() == null)
148          {
149            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
150                String.valueOf(attrType), "equality");
151            throw new ConfigException(message);
152          }
153    
154          Indexer equalityIndexer = new EqualityIndexer(attrType);
155          this.equalityIndex = new Index(name + ".equality",
156                                         equalityIndexer,
157                                         state,
158                                         indexEntryLimit,
159                                         cursorEntryLimit,
160                                         false,
161                                         env,
162                                         entryContainer);
163        }
164    
165        if (indexConfig.getIndexType().contains(
166                LocalDBIndexCfgDefn.IndexType.PRESENCE))
167        {
168          Indexer presenceIndexer = new PresenceIndexer(attrType);
169          this.presenceIndex = new Index(name + ".presence",
170                                         presenceIndexer,
171                                         state,
172                                         indexEntryLimit,
173                                         cursorEntryLimit,
174                                         false,
175                                         env,
176                                         entryContainer);
177        }
178    
179        if (indexConfig.getIndexType().contains(
180                LocalDBIndexCfgDefn.IndexType.SUBSTRING))
181        {
182          if (attrType.getSubstringMatchingRule() == null)
183          {
184            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
185                String.valueOf(attrType), "substring");
186            throw new ConfigException(message);
187          }
188    
189          Indexer substringIndexer = new SubstringIndexer(attrType,
190                                             indexConfig.getSubstringLength());
191          this.substringIndex = new Index(name + ".substring",
192                                         substringIndexer,
193                                         state,
194                                         indexEntryLimit,
195                                         cursorEntryLimit,
196                                         false,
197                                         env,
198                                         entryContainer);
199        }
200    
201        if (indexConfig.getIndexType().contains(
202                LocalDBIndexCfgDefn.IndexType.ORDERING))
203        {
204          if (attrType.getOrderingMatchingRule() == null)
205          {
206            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
207                String.valueOf(attrType), "ordering");
208            throw new ConfigException(message);
209          }
210    
211          Indexer orderingIndexer = new OrderingIndexer(attrType);
212          this.orderingIndex = new Index(name + ".ordering",
213                                         orderingIndexer,
214                                         state,
215                                         indexEntryLimit,
216                                         cursorEntryLimit,
217                                         false,
218                                         env,
219                                         entryContainer);
220        }
221        if (indexConfig.getIndexType().contains(
222            LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
223        {
224          if (attrType.getApproximateMatchingRule() == null)
225          {
226            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
227                String.valueOf(attrType), "approximate");
228            throw new ConfigException(message);
229          }
230    
231          Indexer approximateIndexer = new ApproximateIndexer(attrType);
232          this.approximateIndex = new Index(name + ".approximate",
233                                            approximateIndexer,
234                                            state,
235                                            indexEntryLimit,
236                                            cursorEntryLimit,
237                                            false,
238                                            env,
239                                            entryContainer);
240        }
241    
242        this.indexConfig.addChangeListener(this);
243      }
244    
245      /**
246       * Open the attribute index.
247       *
248       * @throws DatabaseException if a JE database error occurs while
249       * openning the index.
250       */
251      public void open() throws DatabaseException
252      {
253        if (equalityIndex != null)
254        {
255          equalityIndex.open();
256        }
257    
258        if (presenceIndex != null)
259        {
260          presenceIndex.open();
261        }
262    
263        if (substringIndex != null)
264        {
265          substringIndex.open();
266        }
267    
268        if (orderingIndex != null)
269        {
270          orderingIndex.open();
271        }
272    
273        if (approximateIndex != null)
274        {
275          approximateIndex.open();
276        }
277      }
278    
279      /**
280       * Close the attribute index.
281       *
282       * @throws DatabaseException if a JE database error occurs while
283       * closing the index.
284       */
285      public void close() throws DatabaseException
286      {
287        if (equalityIndex != null)
288        {
289          equalityIndex.close();
290        }
291    
292        if (presenceIndex != null)
293        {
294          presenceIndex.close();
295        }
296    
297        if (substringIndex != null)
298        {
299          substringIndex.close();
300        }
301    
302        if (orderingIndex != null)
303        {
304          orderingIndex.close();
305        }
306    
307        if (approximateIndex != null)
308        {
309          approximateIndex.close();
310        }
311    
312        indexConfig.removeChangeListener(this);
313        // The entryContainer is responsible for closing the JE databases.
314      }
315    
316      /**
317       * Get the attribute type of this attribute index.
318       * @return The attribute type of this attribute index.
319       */
320      public AttributeType getAttributeType()
321      {
322        return indexConfig.getAttribute();
323      }
324    
325      /**
326       * Get the JE index configuration used by this index.
327       * @return The configuration in effect.
328       */
329      public LocalDBIndexCfg getConfiguration()
330      {
331        return indexConfig;
332      }
333    
334      /**
335       * Update the attribute index for a new entry.
336       *
337       * @param buffer The index buffer to use to store the added keys
338       * @param entryID     The entry ID.
339       * @param entry       The contents of the new entry.
340       * @return True if all the index keys for the entry are added. False if the
341       *         entry ID already exists for some keys.
342       * @throws DatabaseException If an error occurs in the JE database.
343       * @throws DirectoryException If a Directory Server error occurs.
344       * @throws JebException If an error occurs in the JE backend.
345       */
346      public boolean addEntry(IndexBuffer buffer, EntryID entryID,
347                              Entry entry)
348           throws DatabaseException, DirectoryException, JebException
349      {
350        boolean success = true;
351    
352        if (equalityIndex != null)
353        {
354          if(!equalityIndex.addEntry(buffer, entryID, entry))
355          {
356            success = false;
357          }
358        }
359    
360        if (presenceIndex != null)
361        {
362          if(!presenceIndex.addEntry(buffer, entryID, entry))
363          {
364            success = false;
365          }
366        }
367    
368        if (substringIndex != null)
369        {
370          if(!substringIndex.addEntry(buffer, entryID, entry))
371          {
372            success = false;
373          }
374        }
375    
376        if (orderingIndex != null)
377        {
378          if(!orderingIndex.addEntry(buffer, entryID, entry))
379          {
380            success = false;
381          }
382        }
383    
384        if (approximateIndex != null)
385        {
386          if(!approximateIndex.addEntry(buffer, entryID, entry))
387          {
388            success = false;
389          }
390        }
391    
392        return success;
393      }
394    
395    
396      /**
397       * Update the attribute index for a new entry.
398       *
399       * @param txn         The database transaction to be used for the insertions.
400       * @param entryID     The entry ID.
401       * @param entry       The contents of the new entry.
402       * @return True if all the index keys for the entry are added. False if the
403       *         entry ID already exists for some keys.
404       * @throws DatabaseException If an error occurs in the JE database.
405       * @throws DirectoryException If a Directory Server error occurs.
406       * @throws JebException If an error occurs in the JE backend.
407       */
408      public boolean addEntry(Transaction txn, EntryID entryID, Entry entry)
409           throws DatabaseException, DirectoryException, JebException
410      {
411        boolean success = true;
412    
413        if (equalityIndex != null)
414        {
415          if(!equalityIndex.addEntry(txn, entryID, entry))
416          {
417            success = false;
418          }
419        }
420    
421        if (presenceIndex != null)
422        {
423          if(!presenceIndex.addEntry(txn, entryID, entry))
424          {
425            success = false;
426          }
427        }
428    
429        if (substringIndex != null)
430        {
431          if(!substringIndex.addEntry(txn, entryID, entry))
432          {
433            success = false;
434          }
435        }
436    
437        if (orderingIndex != null)
438        {
439          if(!orderingIndex.addEntry(txn, entryID, entry))
440          {
441            success = false;
442          }
443        }
444    
445        if (approximateIndex != null)
446        {
447          if(!approximateIndex.addEntry(txn, entryID, entry))
448          {
449            success = false;
450          }
451        }
452    
453        return success;
454      }
455    
456      /**
457       * Update the attribute index for a deleted entry.
458       *
459       * @param buffer The index buffer to use to store the deleted keys
460       * @param entryID     The entry ID
461       * @param entry       The contents of the deleted entry.
462       * @throws DatabaseException If an error occurs in the JE database.
463       * @throws DirectoryException If a Directory Server error occurs.
464       * @throws JebException If an error occurs in the JE backend.
465       */
466      public void removeEntry(IndexBuffer buffer, EntryID entryID,
467                              Entry entry)
468           throws DatabaseException, DirectoryException, JebException
469      {
470        if (equalityIndex != null)
471        {
472          equalityIndex.removeEntry(buffer, entryID, entry);
473        }
474    
475        if (presenceIndex != null)
476        {
477          presenceIndex.removeEntry(buffer, entryID, entry);
478        }
479    
480        if (substringIndex != null)
481        {
482          substringIndex.removeEntry(buffer, entryID, entry);
483        }
484    
485        if (orderingIndex != null)
486        {
487          orderingIndex.removeEntry(buffer, entryID, entry);
488        }
489    
490        if(approximateIndex != null)
491        {
492          approximateIndex.removeEntry(buffer, entryID, entry);
493        }
494      }
495    
496      /**
497       * Update the attribute index for a deleted entry.
498       *
499       * @param txn         The database transaction to be used for the deletions
500       * @param entryID     The entry ID
501       * @param entry       The contents of the deleted entry.
502       * @throws DatabaseException If an error occurs in the JE database.
503       * @throws DirectoryException If a Directory Server error occurs.
504       * @throws JebException If an error occurs in the JE backend.
505       */
506      public void removeEntry(Transaction txn, EntryID entryID, Entry entry)
507           throws DatabaseException, DirectoryException, JebException
508      {
509        if (equalityIndex != null)
510        {
511          equalityIndex.removeEntry(txn, entryID, entry);
512        }
513    
514        if (presenceIndex != null)
515        {
516          presenceIndex.removeEntry(txn, entryID, entry);
517        }
518    
519        if (substringIndex != null)
520        {
521          substringIndex.removeEntry(txn, entryID, entry);
522        }
523    
524        if (orderingIndex != null)
525        {
526          orderingIndex.removeEntry(txn, entryID, entry);
527        }
528    
529        if(approximateIndex != null)
530        {
531          approximateIndex.removeEntry(txn, entryID, entry);
532        }
533      }
534    
535      /**
536       * Update the index to reflect a sequence of modifications in a Modify
537       * operation.
538       *
539       * @param txn The JE transaction to use for database updates.
540       * @param entryID The ID of the entry that was modified.
541       * @param oldEntry The entry before the modifications were applied.
542       * @param newEntry The entry after the modifications were applied.
543       * @param mods The sequence of modifications in the Modify operation.
544       * @throws DatabaseException If an error occurs during an operation on a
545       * JE database.
546       */
547      public void modifyEntry(Transaction txn,
548                              EntryID entryID,
549                              Entry oldEntry,
550                              Entry newEntry,
551                              List<Modification> mods)
552           throws DatabaseException
553      {
554        if (equalityIndex != null)
555        {
556          equalityIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
557        }
558    
559        if (presenceIndex != null)
560        {
561          presenceIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
562        }
563    
564        if (substringIndex != null)
565        {
566          substringIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
567        }
568    
569        if (orderingIndex != null)
570        {
571          orderingIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
572        }
573    
574        if (approximateIndex != null)
575        {
576          approximateIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
577        }
578      }
579    
580      /**
581       * Update the index to reflect a sequence of modifications in a Modify
582       * operation.
583       *
584       * @param buffer The index buffer used to buffer up the index changes.
585       * @param entryID The ID of the entry that was modified.
586       * @param oldEntry The entry before the modifications were applied.
587       * @param newEntry The entry after the modifications were applied.
588       * @param mods The sequence of modifications in the Modify operation.
589       * @throws DatabaseException If an error occurs during an operation on a
590       * JE database.
591       */
592      public void modifyEntry(IndexBuffer buffer,
593                              EntryID entryID,
594                              Entry oldEntry,
595                              Entry newEntry,
596                              List<Modification> mods)
597           throws DatabaseException
598      {
599        if (equalityIndex != null)
600        {
601          equalityIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
602        }
603    
604        if (presenceIndex != null)
605        {
606          presenceIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
607        }
608    
609        if (substringIndex != null)
610        {
611          substringIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
612        }
613    
614        if (orderingIndex != null)
615        {
616          orderingIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
617        }
618    
619        if (approximateIndex != null)
620        {
621          approximateIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
622        }
623      }
624    
625      /**
626       * Makes a byte array representing a substring index key for
627       * one substring of a value.
628       *
629       * @param bytes The byte array containing the value.
630       * @param pos The starting position of the substring.
631       * @param len The length of the substring.
632       * @return A byte array containing a substring key.
633       */
634      private byte[] makeSubstringKey(byte[] bytes, int pos, int len)
635      {
636        byte[] keyBytes = new byte[len];
637        System.arraycopy(bytes, pos, keyBytes, 0, len);
638        return keyBytes;
639      }
640    
641      /**
642       * Decompose an attribute value into a set of substring index keys.
643       * The ID of the entry containing this value should be inserted
644       * into the list of each of these keys.
645       *
646       * @param value A byte array containing the normalized attribute value.
647       * @return A set of index keys.
648       */
649      Set<ByteString> substringKeys(byte[] value)
650      {
651        // Eliminate duplicates by putting the keys into a set.
652        // Sorting the keys will ensure database record locks are acquired
653        // in a consistent order and help prevent transaction deadlocks between
654        // concurrent writers.
655        Set<ByteString> set = new HashSet<ByteString>();
656    
657        int substrLength = indexConfig.getSubstringLength();
658        byte[] keyBytes;
659    
660        // Example: The value is ABCDE and the substring length is 3.
661        // We produce the keys ABC BCD CDE DE E
662        // To find values containing a short substring such as DE,
663        // iterate through keys with prefix DE. To find values
664        // containing a longer substring such as BCDE, read keys
665        // BCD and CDE.
666        for (int i = 0, remain = value.length; remain > 0; i++, remain--)
667        {
668          int len = Math.min(substrLength, remain);
669          keyBytes = makeSubstringKey(value, i, len);
670          set.add(new ASN1OctetString(keyBytes));
671        }
672    
673        return set;
674      }
675    
676      /**
677       * Retrieve the entry IDs that might contain a given substring.
678       * @param bytes A normalized substring of an attribute value.
679       * @return The candidate entry IDs.
680       */
681      private EntryIDSet matchSubstring(byte[] bytes)
682      {
683        int substrLength = indexConfig.getSubstringLength();
684    
685        // There are two cases, depending on whether the user-provided
686        // substring is smaller than the configured index substring length or not.
687        if (bytes.length < substrLength)
688        {
689          // Iterate through all the keys that have this value as the prefix.
690    
691          // Set the lower bound for a range search.
692          byte[] lower = makeSubstringKey(bytes, 0, bytes.length);
693    
694          // Set the upper bound for a range search.
695          // We need a key for the upper bound that is of equal length
696          // but slightly greater than the lower bound.
697          byte[] upper = makeSubstringKey(bytes, 0, bytes.length);
698          for (int i = upper.length-1; i >= 0; i--)
699          {
700            if (upper[i] == 0xFF)
701            {
702              // We have to carry the overflow to the more significant byte.
703              upper[i] = 0;
704            }
705            else
706            {
707              // No overflow, we can stop.
708              upper[i] = (byte) (upper[i] + 1);
709              break;
710            }
711          }
712    
713          // Read the range: lower <= keys < upper.
714          return substringIndex.readRange(lower, upper, true, false);
715        }
716        else
717        {
718          // Break the value up into fragments of length equal to the
719          // index substring length, and read those keys.
720    
721          // Eliminate duplicates by putting the keys into a set.
722          Set<byte[]> set =
723              new TreeSet<byte[]>(substringIndex.indexer.getComparator());
724    
725          // Example: The value is ABCDE and the substring length is 3.
726          // We produce the keys ABC BCD CDE.
727          for (int first = 0, last = substrLength;
728               last <= bytes.length; first++, last++)
729          {
730            byte[] keyBytes;
731            keyBytes = makeSubstringKey(bytes, first, substrLength);
732            set.add(keyBytes);
733          }
734    
735          EntryIDSet results = new EntryIDSet();
736          DatabaseEntry key = new DatabaseEntry();
737          for (byte[] keyBytes : set)
738          {
739            // Read the key.
740            key.setData(keyBytes);
741            EntryIDSet list = substringIndex.readKey(key, null, LockMode.DEFAULT);
742    
743            // Incorporate them into the results.
744            results.retainAll(list);
745    
746            // We may have reached the point of diminishing returns where
747            // it is quicker to stop now and process the current small number of
748            // candidates.
749            if (results.isDefined() &&
750                 results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
751            {
752              break;
753            }
754          }
755    
756          return results;
757        }
758      }
759    
760      /**
761       * Uses an equality index to retrieve the entry IDs that might contain a
762       * given initial substring.
763       * @param bytes A normalized initial substring of an attribute value.
764       * @return The candidate entry IDs.
765       */
766      private EntryIDSet matchInitialSubstring(byte[] bytes)
767      {
768        // Iterate through all the keys that have this value as the prefix.
769    
770        // Set the lower bound for a range search.
771        byte[] lower = bytes;
772    
773        // Set the upper bound for a range search.
774        // We need a key for the upper bound that is of equal length
775        // but slightly greater than the lower bound.
776        byte[] upper = new byte[bytes.length];
777        System.arraycopy(bytes,0, upper, 0, bytes.length);
778    
779        for (int i = upper.length-1; i >= 0; i--)
780        {
781          if (upper[i] == 0xFF)
782          {
783            // We have to carry the overflow to the more significant byte.
784            upper[i] = 0;
785          }
786          else
787          {
788            // No overflow, we can stop.
789            upper[i] = (byte) (upper[i] + 1);
790            break;
791          }
792        }
793    
794        // Read the range: lower <= keys < upper.
795        return equalityIndex.readRange(lower, upper, true, false);
796      }
797    
798      /**
799       * Retrieve the entry IDs that might match an equality filter.
800       *
801       * @param equalityFilter The equality filter.
802       * @param debugBuffer If not null, a diagnostic string will be written
803       *                     which will help determine how the indexes contributed
804       *                     to this search.
805       * @return The candidate entry IDs that might contain the filter
806       *         assertion value.
807       */
808      public EntryIDSet evaluateEqualityFilter(SearchFilter equalityFilter,
809                                               StringBuilder debugBuffer)
810      {
811        if (equalityIndex == null)
812        {
813          return new EntryIDSet();
814        }
815    
816        try
817        {
818          // Make a key from the normalized assertion value.
819          byte[] keyBytes =
820               equalityFilter.getAssertionValue().getNormalizedValue().value();
821          DatabaseEntry key = new DatabaseEntry(keyBytes);
822    
823          if(debugBuffer != null)
824          {
825            debugBuffer.append("[INDEX:");
826            debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
827            debugBuffer.append(".");
828            debugBuffer.append("equality]");
829          }
830    
831          // Read the key.
832          return equalityIndex.readKey(key, null, LockMode.DEFAULT);
833        }
834        catch (DirectoryException e)
835        {
836          if (debugEnabled())
837          {
838            TRACER.debugCaught(DebugLogLevel.ERROR, e);
839          }
840          return new EntryIDSet();
841        }
842      }
843    
844      /**
845       * Retrieve the entry IDs that might match a presence filter.
846       *
847       * @param filter The presence filter.
848       * @param debugBuffer If not null, a diagnostic string will be written
849       *                     which will help determine how the indexes contributed
850       *                     to this search.
851       * @return The candidate entry IDs that might contain one or more
852       *         values of the attribute type in the filter.
853       */
854      public EntryIDSet evaluatePresenceFilter(SearchFilter filter,
855                                               StringBuilder debugBuffer)
856      {
857        if (presenceIndex == null)
858        {
859          return new EntryIDSet();
860        }
861    
862        if(debugBuffer != null)
863        {
864          debugBuffer.append("[INDEX:");
865          debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
866          debugBuffer.append(".");
867          debugBuffer.append("presence]");
868        }
869    
870        // Read the presence key
871        return presenceIndex.readKey(presenceKey, null, LockMode.DEFAULT);
872      }
873    
874      /**
875       * Retrieve the entry IDs that might match a greater-or-equal filter.
876       *
877       * @param filter The greater-or-equal filter.
878       * @param debugBuffer If not null, a diagnostic string will be written
879       *                     which will help determine how the indexes contributed
880       *                     to this search.
881       * @return The candidate entry IDs that might contain a value
882       *         greater than or equal to the filter assertion value.
883       */
884      public EntryIDSet evaluateGreaterOrEqualFilter(SearchFilter filter,
885                                                     StringBuilder debugBuffer)
886      {
887        if (orderingIndex == null)
888        {
889          return new EntryIDSet();
890        }
891    
892        try
893        {
894          // Set the lower bound for a range search.
895          // Use the ordering matching rule to normalize the value.
896          OrderingMatchingRule orderingRule =
897               filter.getAttributeType().getOrderingMatchingRule();
898          byte[] lower = orderingRule.normalizeValue(
899               filter.getAssertionValue().getValue()).value();
900    
901          // Set the upper bound to 0 to search all keys greater then the lower
902          // bound.
903          byte[] upper = new byte[0];
904    
905          if(debugBuffer != null)
906          {
907            debugBuffer.append("[INDEX:");
908            debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
909            debugBuffer.append(".");
910            debugBuffer.append("ordering]");
911          }
912    
913          // Read the range: lower <= keys < upper.
914          return orderingIndex.readRange(lower, upper, true, false);
915        }
916        catch (DirectoryException e)
917        {
918          if (debugEnabled())
919          {
920            TRACER.debugCaught(DebugLogLevel.ERROR, e);
921          }
922          return new EntryIDSet();
923        }
924      }
925    
926      /**
927       * Retrieve the entry IDs that might match a less-or-equal filter.
928       *
929       * @param filter The less-or-equal filter.
930       * @param debugBuffer If not null, a diagnostic string will be written
931       *                     which will help determine how the indexes contributed
932       *                     to this search.
933       * @return The candidate entry IDs that might contain a value
934       *         less than or equal to the filter assertion value.
935       */
936      public EntryIDSet evaluateLessOrEqualFilter(SearchFilter filter,
937                                                  StringBuilder debugBuffer)
938      {
939        if (orderingIndex == null)
940        {
941          return new EntryIDSet();
942        }
943    
944        try
945        {
946          // Set the lower bound to 0 to start the range search from the smallest
947          // key.
948          byte[] lower = new byte[0];
949    
950          // Set the upper bound for a range search.
951          // Use the ordering matching rule to normalize the value.
952          OrderingMatchingRule orderingRule =
953               filter.getAttributeType().getOrderingMatchingRule();
954          byte[] upper = orderingRule.normalizeValue(
955               filter.getAssertionValue().getValue()).value();
956    
957          if(debugBuffer != null)
958          {
959            debugBuffer.append("[INDEX:");
960            debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
961            debugBuffer.append(".");
962            debugBuffer.append("ordering]");
963          }
964    
965          // Read the range: lower < keys <= upper.
966          return orderingIndex.readRange(lower, upper, false, true);
967        }
968        catch (DirectoryException e)
969        {
970          if (debugEnabled())
971          {
972            TRACER.debugCaught(DebugLogLevel.ERROR, e);
973          }
974          return new EntryIDSet();
975        }
976      }
977    
978      /**
979       * Retrieve the entry IDs that might match a substring filter.
980       *
981       * @param filter The substring filter.
982       * @param debugBuffer If not null, a diagnostic string will be written
983       *                     which will help determine how the indexes contributed
984       *                     to this search.
985       * @return The candidate entry IDs that might contain a value
986       *         that matches the filter substrings.
987       */
988      public EntryIDSet evaluateSubstringFilter(SearchFilter filter,
989                                                StringBuilder debugBuffer)
990      {
991        SubstringMatchingRule matchRule =
992             filter.getAttributeType().getSubstringMatchingRule();
993    
994        try
995        {
996          ArrayList<ByteString> elements = new ArrayList<ByteString>();
997          EntryIDSet results = new EntryIDSet();
998    
999          if (filter.getSubInitialElement() != null)
1000          {
1001            // Use the equality index for initial substrings if possible.
1002            if (equalityIndex != null)
1003            {
1004              ByteString normValue =
1005                   matchRule.normalizeSubstring(filter.getSubInitialElement());
1006              byte[] normBytes = normValue.value();
1007    
1008              EntryIDSet list = matchInitialSubstring(normBytes);
1009              results.retainAll(list);
1010    
1011              if (results.isDefined() &&
1012                   results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
1013              {
1014    
1015                if(debugBuffer != null)
1016                {
1017                  debugBuffer.append("[INDEX:");
1018                  debugBuffer.append(indexConfig.getAttribute().
1019                      getNameOrOID());
1020                  debugBuffer.append(".");
1021                  debugBuffer.append("equality]");
1022                }
1023    
1024                return results;
1025              }
1026            }
1027            else
1028            {
1029              elements.add(filter.getSubInitialElement());
1030            }
1031          }
1032    
1033          if (substringIndex == null)
1034          {
1035            return results;
1036          }
1037    
1038          // We do not distinguish between sub and final elements
1039          // in the substring index. Put all the elements into a single list.
1040          elements.addAll(filter.getSubAnyElements());
1041          if (filter.getSubFinalElement() != null)
1042          {
1043            elements.add(filter.getSubFinalElement());
1044          }
1045    
1046          // Iterate through each substring element.
1047          for (ByteString element : elements)
1048          {
1049            // Normalize the substring according to the substring matching rule.
1050            ByteString normValue = matchRule.normalizeSubstring(element);
1051            byte[] normBytes = normValue.value();
1052    
1053            // Get the candidate entry IDs from the index.
1054            EntryIDSet list = matchSubstring(normBytes);
1055    
1056            // Incorporate them into the results.
1057            results.retainAll(list);
1058    
1059            // We may have reached the point of diminishing returns where
1060            // it is quicker to stop now and process the current small number of
1061            // candidates.
1062            if (results.isDefined() &&
1063                 results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
1064            {
1065              break;
1066            }
1067          }
1068    
1069          if(debugBuffer != null)
1070          {
1071            debugBuffer.append("[INDEX:");
1072            debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
1073            debugBuffer.append(".");
1074            debugBuffer.append("substring]");
1075          }
1076    
1077          return results;
1078        }
1079        catch (DirectoryException e)
1080        {
1081          if (debugEnabled())
1082          {
1083            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1084          }
1085          return new EntryIDSet();
1086        }
1087      }
1088    
1089      /**
1090       * Retrieve the entry IDs that might have a value greater than or
1091       * equal to the lower bound value, and less than or equal to the
1092       * upper bound value.
1093       *
1094       * @param lowerValue The lower bound value
1095       * @param upperValue The upper bound value
1096       * @return The candidate entry IDs.
1097       */
1098      public EntryIDSet evaluateBoundedRange(AttributeValue lowerValue,
1099                                              AttributeValue upperValue)
1100      {
1101        if (orderingIndex == null)
1102        {
1103          return new EntryIDSet();
1104        }
1105    
1106        try
1107        {
1108          // Use the ordering matching rule to normalize the values.
1109          OrderingMatchingRule orderingRule =
1110               getAttributeType().getOrderingMatchingRule();
1111    
1112          // Set the lower bound for a range search.
1113          byte[] lower = orderingRule.normalizeValue(lowerValue.getValue()).value();
1114    
1115          // Set the upper bound for a range search.
1116          byte[] upper = orderingRule.normalizeValue(upperValue.getValue()).value();
1117    
1118          // Read the range: lower <= keys <= upper.
1119          return orderingIndex.readRange(lower, upper, true, true);
1120        }
1121        catch (DirectoryException e)
1122        {
1123          if (debugEnabled())
1124          {
1125            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1126          }
1127          return new EntryIDSet();
1128        }
1129      }
1130    
1131      /**
1132       * The default lexicographic byte array comparator.
1133       * Is there one available in the Java platform?
1134       */
1135      static public class KeyComparator implements Comparator<byte[]>
1136      {
1137        /**
1138         * Compares its two arguments for order.  Returns a negative integer,
1139         * zero, or a positive integer as the first argument is less than, equal
1140         * to, or greater than the second.
1141         *
1142         * @param a the first object to be compared.
1143         * @param b the second object to be compared.
1144         * @return a negative integer, zero, or a positive integer as the
1145         *         first argument is less than, equal to, or greater than the
1146         *         second.
1147         */
1148        public int compare(byte[] a, byte[] b)
1149        {
1150          int i;
1151          for (i = 0; i < a.length && i < b.length; i++)
1152          {
1153            if (a[i] > b[i])
1154            {
1155              return 1;
1156            }
1157            else if (a[i] < b[i])
1158            {
1159              return -1;
1160            }
1161          }
1162          if (a.length == b.length)
1163          {
1164            return 0;
1165          }
1166          if (a.length > b.length)
1167          {
1168            return 1;
1169          }
1170          else
1171          {
1172            return -1;
1173          }
1174        }
1175      }
1176    
1177      /**
1178       * Retrieve the entry IDs that might match an approximate filter.
1179       *
1180       * @param approximateFilter The approximate filter.
1181       * @param debugBuffer If not null, a diagnostic string will be written
1182       *                     which will help determine how the indexes contributed
1183       *                     to this search.
1184       * @return The candidate entry IDs that might contain the filter
1185       *         assertion value.
1186       */
1187      public EntryIDSet evaluateApproximateFilter(SearchFilter approximateFilter,
1188                                                  StringBuilder debugBuffer)
1189      {
1190        if (approximateIndex == null)
1191        {
1192          return new EntryIDSet();
1193        }
1194    
1195        try
1196        {
1197          ApproximateMatchingRule approximateMatchingRule =
1198              approximateFilter.getAttributeType().getApproximateMatchingRule();
1199          // Make a key from the normalized assertion value.
1200          byte[] keyBytes =
1201               approximateMatchingRule.normalizeValue(
1202                   approximateFilter.getAssertionValue().getValue()).value();
1203          DatabaseEntry key = new DatabaseEntry(keyBytes);
1204    
1205          if(debugBuffer != null)
1206          {
1207            debugBuffer.append("[INDEX:");
1208            debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
1209            debugBuffer.append(".");
1210            debugBuffer.append("approximate]");
1211          }
1212    
1213          // Read the key.
1214          return approximateIndex.readKey(key, null, LockMode.DEFAULT);
1215        }
1216        catch (DirectoryException e)
1217        {
1218          if (debugEnabled())
1219          {
1220            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1221          }
1222          return new EntryIDSet();
1223        }
1224      }
1225    
1226      /**
1227       * Return the number of values that have exceeded the entry limit since this
1228       * object was created.
1229       *
1230       * @return The number of values that have exceeded the entry limit.
1231       */
1232      public long getEntryLimitExceededCount()
1233      {
1234        long entryLimitExceededCount = 0;
1235    
1236        if (equalityIndex != null)
1237        {
1238          entryLimitExceededCount += equalityIndex.getEntryLimitExceededCount();
1239        }
1240    
1241        if (presenceIndex != null)
1242        {
1243          entryLimitExceededCount += presenceIndex.getEntryLimitExceededCount();
1244        }
1245    
1246        if (substringIndex != null)
1247        {
1248          entryLimitExceededCount += substringIndex.getEntryLimitExceededCount();
1249        }
1250    
1251        if (orderingIndex != null)
1252        {
1253          entryLimitExceededCount += orderingIndex.getEntryLimitExceededCount();
1254        }
1255    
1256        if (approximateIndex != null)
1257        {
1258          entryLimitExceededCount +=
1259              approximateIndex.getEntryLimitExceededCount();
1260        }
1261    
1262        return entryLimitExceededCount;
1263      }
1264    
1265      /**
1266       * Get a list of the databases opened by this attribute index.
1267       * @param dbList A list of database containers.
1268       */
1269      public void listDatabases(List<DatabaseContainer> dbList)
1270      {
1271        if (equalityIndex != null)
1272        {
1273          dbList.add(equalityIndex);
1274        }
1275    
1276        if (presenceIndex != null)
1277        {
1278          dbList.add(presenceIndex);
1279        }
1280    
1281        if (substringIndex != null)
1282        {
1283          dbList.add(substringIndex);
1284        }
1285    
1286        if (orderingIndex != null)
1287        {
1288          dbList.add(orderingIndex);
1289        }
1290    
1291        if (approximateIndex != null)
1292        {
1293          dbList.add(approximateIndex);
1294        }
1295      }
1296    
1297      /**
1298       * Get a string representation of this object.
1299       * @return return A string representation of this object.
1300       */
1301      public String toString()
1302      {
1303        return getName();
1304      }
1305    
1306      /**
1307       * {@inheritDoc}
1308       */
1309      public synchronized boolean isConfigurationChangeAcceptable(
1310          LocalDBIndexCfg cfg,
1311          List<Message> unacceptableReasons)
1312      {
1313        AttributeType attrType = cfg.getAttribute();
1314    
1315        if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.EQUALITY))
1316        {
1317          if (equalityIndex == null && attrType.getEqualityMatchingRule() == null)
1318          {
1319            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
1320                    String.valueOf(String.valueOf(attrType)), "equality");
1321            unacceptableReasons.add(message);
1322            return false;
1323          }
1324        }
1325    
1326        if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.SUBSTRING))
1327        {
1328          if (substringIndex == null && attrType.getSubstringMatchingRule() == null)
1329          {
1330            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
1331                    String.valueOf(attrType), "substring");
1332            unacceptableReasons.add(message);
1333            return false;
1334          }
1335    
1336        }
1337    
1338        if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.ORDERING))
1339        {
1340          if (orderingIndex == null && attrType.getOrderingMatchingRule() == null)
1341          {
1342            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
1343                    String.valueOf(attrType), "ordering");
1344            unacceptableReasons.add(message);
1345            return false;
1346          }
1347        }
1348        if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
1349        {
1350          if (approximateIndex == null &&
1351              attrType.getApproximateMatchingRule() == null)
1352          {
1353            Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
1354                    String.valueOf(attrType), "approximate");
1355            unacceptableReasons.add(message);
1356            return false;
1357          }
1358        }
1359    
1360        return true;
1361      }
1362    
1363      /**
1364       * {@inheritDoc}
1365       */
1366      public synchronized ConfigChangeResult applyConfigurationChange(
1367          LocalDBIndexCfg cfg)
1368      {
1369        ConfigChangeResult ccr;
1370        boolean adminActionRequired = false;
1371        ArrayList<Message> messages = new ArrayList<Message>();
1372        try
1373        {
1374          AttributeType attrType = cfg.getAttribute();
1375          String name =
1376            entryContainer.getDatabasePrefix() + "_" + attrType.getNameOrOID();
1377          int indexEntryLimit = cfg.getIndexEntryLimit();
1378    
1379          if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.EQUALITY))
1380          {
1381            if (equalityIndex == null)
1382            {
1383              // Adding equality index
1384              Indexer equalityIndexer = new EqualityIndexer(attrType);
1385              equalityIndex = new Index(name + ".equality",
1386                                        equalityIndexer,
1387                                        state,
1388                                        indexEntryLimit,
1389                                        cursorEntryLimit,
1390                                        false,
1391                                        env,
1392                                        entryContainer);
1393              equalityIndex.open();
1394    
1395              adminActionRequired = true;
1396              messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
1397                      name + ".equality"));
1398    
1399            }
1400            else
1401            {
1402              // already exists. Just update index entry limit.
1403              if(this.equalityIndex.setIndexEntryLimit(indexEntryLimit))
1404              {
1405    
1406                adminActionRequired = true;
1407                Message message =
1408                        NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
1409                                name + ".equality");
1410                messages.add(message);
1411                this.equalityIndex.setIndexEntryLimit(indexEntryLimit);
1412              }
1413            }
1414          }
1415          else
1416          {
1417            if (equalityIndex != null)
1418            {
1419              entryContainer.exclusiveLock.lock();
1420              try
1421              {
1422                entryContainer.deleteDatabase(equalityIndex);
1423                equalityIndex = null;
1424              }
1425              catch(DatabaseException de)
1426              {
1427                messages.add(Message.raw(
1428                        StaticUtils.stackTraceToSingleLineString(de)));
1429                ccr = new ConfigChangeResult(
1430                    DirectoryServer.getServerErrorResultCode(), false, messages);
1431                return ccr;
1432              }
1433              finally
1434              {
1435                entryContainer.exclusiveLock.unlock();
1436              }
1437            }
1438          }
1439    
1440          if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.PRESENCE))
1441          {
1442            if(presenceIndex == null)
1443            {
1444              Indexer presenceIndexer = new PresenceIndexer(attrType);
1445              presenceIndex = new Index(name + ".presence",
1446                                        presenceIndexer,
1447                                        state,
1448                                        indexEntryLimit,
1449                                        cursorEntryLimit,
1450                                        false,
1451                                        env,
1452                                        entryContainer);
1453              presenceIndex.open();
1454    
1455              adminActionRequired = true;
1456    
1457              messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
1458                      name + ".presence"));
1459            }
1460            else
1461            {
1462              // already exists. Just update index entry limit.
1463              if(this.presenceIndex.setIndexEntryLimit(indexEntryLimit))
1464              {
1465                adminActionRequired = true;
1466    
1467                Message message =
1468                        NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
1469                                name + ".presence");
1470                messages.add(message);
1471              }
1472            }
1473          }
1474          else
1475          {
1476            if (presenceIndex != null)
1477            {
1478              entryContainer.exclusiveLock.lock();
1479              try
1480              {
1481                entryContainer.deleteDatabase(presenceIndex);
1482                presenceIndex = null;
1483              }
1484              catch(DatabaseException de)
1485              {
1486                messages.add(Message.raw(
1487                        StaticUtils.stackTraceToSingleLineString(de)));
1488                ccr = new ConfigChangeResult(
1489                    DirectoryServer.getServerErrorResultCode(), false, messages);
1490                return ccr;
1491              }
1492              finally
1493              {
1494                entryContainer.exclusiveLock.unlock();
1495              }
1496            }
1497          }
1498    
1499          if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.SUBSTRING))
1500          {
1501            if(substringIndex == null)
1502            {
1503              Indexer substringIndexer = new SubstringIndexer(
1504                  attrType, cfg.getSubstringLength());
1505              substringIndex = new Index(name + ".substring",
1506                                         substringIndexer,
1507                                         state,
1508                                         indexEntryLimit,
1509                                         cursorEntryLimit,
1510                                         false,
1511                                         env,
1512                                         entryContainer);
1513              substringIndex.open();
1514    
1515              adminActionRequired = true;
1516              messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
1517                      name + ".substring"));
1518            }
1519            else
1520            {
1521              // already exists. Just update index entry limit.
1522              if(this.substringIndex.setIndexEntryLimit(indexEntryLimit))
1523              {
1524                adminActionRequired = true;
1525                Message message =
1526                        NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
1527                                name + ".substring");
1528                messages.add(message);
1529              }
1530    
1531              if(indexConfig.getSubstringLength() !=
1532                  cfg.getSubstringLength())
1533              {
1534                Indexer substringIndexer = new SubstringIndexer(
1535                    attrType, cfg.getSubstringLength());
1536                this.substringIndex.setIndexer(substringIndexer);
1537              }
1538            }
1539          }
1540          else
1541          {
1542            if (substringIndex != null)
1543            {
1544              entryContainer.exclusiveLock.lock();
1545              try
1546              {
1547                entryContainer.deleteDatabase(substringIndex);
1548                substringIndex = null;
1549              }
1550              catch(DatabaseException de)
1551              {
1552                messages.add(Message.raw(
1553                        StaticUtils.stackTraceToSingleLineString(de)));
1554                ccr = new ConfigChangeResult(
1555                    DirectoryServer.getServerErrorResultCode(), false, messages);
1556                return ccr;
1557              }
1558              finally
1559              {
1560                entryContainer.exclusiveLock.unlock();
1561              }
1562            }
1563          }
1564    
1565          if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.ORDERING))
1566          {
1567            if(orderingIndex == null)
1568            {
1569              Indexer orderingIndexer = new OrderingIndexer(attrType);
1570              orderingIndex = new Index(name + ".ordering",
1571                                        orderingIndexer,
1572                                        state,
1573                                        indexEntryLimit,
1574                                        cursorEntryLimit,
1575                                        false,
1576                                        env,
1577                                        entryContainer);
1578              orderingIndex.open();
1579    
1580              adminActionRequired = true;
1581              messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
1582                      name + ".ordering"));
1583            }
1584            else
1585            {
1586              // already exists. Just update index entry limit.
1587              if(this.orderingIndex.setIndexEntryLimit(indexEntryLimit))
1588              {
1589                adminActionRequired = true;
1590    
1591                Message message =
1592                        NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
1593                                name + ".ordering");
1594                messages.add(message);
1595              }
1596            }
1597          }
1598          else
1599          {
1600            if (orderingIndex != null)
1601            {
1602              entryContainer.exclusiveLock.lock();
1603              try
1604              {
1605                entryContainer.deleteDatabase(orderingIndex);
1606                orderingIndex = null;
1607              }
1608              catch(DatabaseException de)
1609              {
1610                messages.add(Message.raw(
1611                        StaticUtils.stackTraceToSingleLineString(de)));
1612                ccr = new ConfigChangeResult(
1613                    DirectoryServer.getServerErrorResultCode(), false, messages);
1614                return ccr;
1615              }
1616              finally
1617              {
1618                entryContainer.exclusiveLock.unlock();
1619              }
1620            }
1621          }
1622    
1623          if (cfg.getIndexType().contains(
1624                  LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
1625          {
1626            if(approximateIndex == null)
1627            {
1628              Indexer approximateIndexer = new ApproximateIndexer(attrType);
1629              approximateIndex = new Index(name + ".approximate",
1630                                           approximateIndexer,
1631                                           state,
1632                                           indexEntryLimit,
1633                                           cursorEntryLimit,
1634                                           false,
1635                                           env,
1636                                           entryContainer);
1637              approximateIndex.open();
1638    
1639              adminActionRequired = true;
1640    
1641              messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
1642                      name + ".approximate"));
1643            }
1644            else
1645            {
1646              // already exists. Just update index entry limit.
1647              if(this.approximateIndex.setIndexEntryLimit(indexEntryLimit))
1648              {
1649                adminActionRequired = true;
1650    
1651                Message message =
1652                        NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
1653                                name + ".approximate");
1654                messages.add(message);
1655              }
1656            }
1657          }
1658          else
1659          {
1660            if (approximateIndex != null)
1661            {
1662              entryContainer.exclusiveLock.lock();
1663              try
1664              {
1665                entryContainer.deleteDatabase(approximateIndex);
1666                approximateIndex = null;
1667              }
1668              catch(DatabaseException de)
1669              {
1670                messages.add(
1671                        Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
1672                ccr = new ConfigChangeResult(
1673                    DirectoryServer.getServerErrorResultCode(), false, messages);
1674                return ccr;
1675              }
1676              finally
1677              {
1678                entryContainer.exclusiveLock.unlock();
1679              }
1680            }
1681          }
1682    
1683          indexConfig = cfg;
1684    
1685          return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
1686                                        messages);
1687        }
1688        catch(Exception e)
1689        {
1690          messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(e)));
1691          ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
1692                                       adminActionRequired,
1693                                       messages);
1694          return ccr;
1695        }
1696      }
1697    
1698      /**
1699       * Set the index truststate.
1700       * @param txn A database transaction, or null if none is required.
1701       * @param trusted True if this index should be trusted or false
1702       *                otherwise.
1703       * @throws DatabaseException If an error occurs in the JE database.
1704       */
1705      public synchronized void setTrusted(Transaction txn, boolean trusted)
1706          throws DatabaseException
1707      {
1708        if (equalityIndex != null)
1709        {
1710          equalityIndex.setTrusted(txn, trusted);
1711        }
1712    
1713        if (presenceIndex != null)
1714        {
1715          presenceIndex.setTrusted(txn, trusted);
1716        }
1717    
1718        if (substringIndex != null)
1719        {
1720          substringIndex.setTrusted(txn, trusted);
1721        }
1722    
1723        if (orderingIndex != null)
1724        {
1725          orderingIndex.setTrusted(txn, trusted);
1726        }
1727    
1728        if (approximateIndex != null)
1729        {
1730          approximateIndex.setTrusted(txn, trusted);
1731        }
1732      }
1733    
1734      /**
1735       * Set the rebuild status of this index.
1736       * @param rebuildRunning True if a rebuild process on this index
1737       *                       is running or False otherwise.
1738       */
1739      public synchronized void setRebuildStatus(boolean rebuildRunning)
1740      {
1741        if (equalityIndex != null)
1742        {
1743          equalityIndex.setRebuildStatus(rebuildRunning);
1744        }
1745    
1746        if (presenceIndex != null)
1747        {
1748          presenceIndex.setRebuildStatus(rebuildRunning);
1749        }
1750    
1751        if (substringIndex != null)
1752        {
1753          substringIndex.setRebuildStatus(rebuildRunning);
1754        }
1755    
1756        if (orderingIndex != null)
1757        {
1758          orderingIndex.setRebuildStatus(rebuildRunning);
1759        }
1760    
1761        if (approximateIndex != null)
1762        {
1763          approximateIndex.setRebuildStatus(rebuildRunning);
1764        }
1765      }
1766    
1767      /**
1768       * Get the JE database name prefix for indexes in this attribute
1769       * index.
1770       *
1771       * @return JE database name for this database container.
1772       */
1773      public String getName()
1774      {
1775        StringBuilder builder = new StringBuilder();
1776        builder.append(entryContainer.getDatabasePrefix());
1777        builder.append("_");
1778        builder.append(indexConfig.getAttribute().getNameOrOID());
1779        return builder.toString();
1780      }
1781    
1782      /**
1783       * Return the equality index.
1784       *
1785       * @return The equality index.
1786       */
1787      public Index getEqualityIndex() {
1788        return  equalityIndex;
1789      }
1790    
1791      /**
1792       * Return the approximate index.
1793       *
1794       * @return The approximate index.
1795       */
1796      public Index getApproximateIndex() {
1797        return approximateIndex;
1798      }
1799    
1800      /**
1801       * Return the ordering index.
1802       *
1803       * @return  The ordering index.
1804       */
1805      public Index getOrderingIndex() {
1806        return orderingIndex;
1807      }
1808    
1809      /**
1810       * Return the substring index.
1811       *
1812       * @return The substring index.
1813       */
1814      public Index getSubstringIndex() {
1815        return substringIndex;
1816      }
1817    
1818      /**
1819       * Return the presence index.
1820       *
1821       * @return The presence index.
1822       */
1823      public Index getPresenceIndex() {
1824        return presenceIndex;
1825      }
1826    
1827      /**
1828       * Retrieves all the indexes used by this attribute index.
1829       *
1830       * @return A collection of all indexes in use by this attribute
1831       * index.
1832       */
1833      public Collection<Index> getAllIndexes() {
1834        LinkedHashSet<Index> indexes = new LinkedHashSet<Index>();
1835    
1836        if (equalityIndex != null)
1837        {
1838          indexes.add(equalityIndex);
1839        }
1840    
1841        if (presenceIndex != null)
1842        {
1843          indexes.add(presenceIndex);
1844        }
1845    
1846        if (substringIndex != null)
1847        {
1848          indexes.add(substringIndex);
1849        }
1850    
1851        if (orderingIndex != null)
1852        {
1853          indexes.add(orderingIndex);
1854        }
1855    
1856        if (approximateIndex != null)
1857        {
1858          indexes.add(approximateIndex);
1859        }
1860    
1861        return indexes;
1862      }
1863    }