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 com.sleepycat.je.*;
031    
032    import org.opends.server.api.*;
033    import org.opends.server.api.plugin.PluginResult;
034    import org.opends.server.core.AddOperation;
035    import org.opends.server.core.DeleteOperation;
036    import org.opends.server.core.DirectoryServer;
037    import org.opends.server.core.PluginConfigManager;
038    import org.opends.server.core.ModifyOperation;
039    import org.opends.server.core.ModifyDNOperation;
040    import org.opends.server.core.SearchOperation;
041    import org.opends.server.protocols.asn1.ASN1OctetString;
042    import org.opends.server.protocols.ldap.LDAPResultCode;
043    import org.opends.server.controls.PagedResultsControl;
044    import org.opends.server.controls.ServerSideSortRequestControl;
045    import org.opends.server.controls.ServerSideSortResponseControl;
046    import org.opends.server.controls.VLVRequestControl;
047    import org.opends.server.types.*;
048    import org.opends.server.util.StaticUtils;
049    import org.opends.server.util.ServerConstants;
050    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
051    
052    import java.util.*;
053    import java.util.concurrent.locks.Lock;
054    import java.util.concurrent.locks.ReentrantReadWriteLock;
055    
056    import static org.opends.messages.JebMessages.*;
057    
058    import org.opends.messages.MessageBuilder;
059    import static org.opends.server.loggers.debug.DebugLogger.*;
060    import org.opends.server.loggers.debug.DebugTracer;
061    import static org.opends.server.util.ServerConstants.*;
062    import org.opends.server.admin.std.server.LocalDBBackendCfg;
063    import org.opends.server.admin.std.server.LocalDBIndexCfg;
064    import org.opends.server.admin.std.server.LocalDBVLVIndexCfg;
065    import org.opends.server.admin.server.ConfigurationChangeListener;
066    import org.opends.server.admin.server.ConfigurationAddListener;
067    import org.opends.server.admin.server.ConfigurationDeleteListener;
068    import org.opends.server.config.ConfigException;
069    
070    /**
071     * Storage container for LDAP entries.  Each base DN of a JE backend is given
072     * its own entry container.  The entry container is the object that implements
073     * the guts of the backend API methods for LDAP operations.
074     */
075    public class EntryContainer
076        implements ConfigurationChangeListener<LocalDBBackendCfg>
077    {
078      /**
079       * The tracer object for the debug logger.
080       */
081      private static final DebugTracer TRACER = getTracer();
082    
083    
084      /**
085       * The name of the entry database.
086       */
087      public static final String ID2ENTRY_DATABASE_NAME = "id2entry";
088    
089      /**
090       * The name of the DN database.
091       */
092      public static final String DN2ID_DATABASE_NAME = "dn2id";
093    
094      /**
095       * The name of the children index database.
096       */
097      public static final String ID2CHILDREN_DATABASE_NAME = "id2children";
098    
099      /**
100       * The name of the subtree index database.
101       */
102      public static final String ID2SUBTREE_DATABASE_NAME = "id2subtree";
103    
104      /**
105       * The name of the referral database.
106       */
107      public static final String REFERRAL_DATABASE_NAME = "referral";
108    
109      /**
110       * The name of the state database.
111       */
112      public static final String STATE_DATABASE_NAME = "state";
113    
114      /**
115       * The attribute used to return a search index debug string to the client.
116       */
117      public static final String ATTR_DEBUG_SEARCH_INDEX = "debugsearchindex";
118    
119      /**
120       * The attribute index configuration manager.
121       */
122      public AttributeJEIndexCfgManager attributeJEIndexCfgManager;
123    
124      /**
125       * The vlv index configuration manager.
126       */
127      public VLVJEIndexCfgManager vlvJEIndexCfgManager;
128    
129      /**
130       * The backend to which this entry entryContainer belongs.
131       */
132      private Backend backend;
133    
134      /**
135       * The root container in which this entryContainer belongs.
136       */
137      private RootContainer rootContainer;
138    
139      /**
140       * The baseDN this entry container is responsible for.
141       */
142      private DN baseDN;
143    
144      /**
145       * The backend configuration.
146       */
147      private LocalDBBackendCfg config;
148    
149      /**
150       * The JE database environment.
151       */
152      private Environment env;
153    
154      /**
155       * The DN database maps a normalized DN string to an entry ID (8 bytes).
156       */
157      private DN2ID dn2id;
158    
159      /**
160       * The entry database maps an entry ID (8 bytes) to a complete encoded entry.
161       */
162      private ID2Entry id2entry;
163    
164      /**
165       * Index maps entry ID to an entry ID list containing its children.
166       */
167      private Index id2children;
168    
169      /**
170       * Index maps entry ID to an entry ID list containing its subordinates.
171       */
172      private Index id2subtree;
173    
174      /**
175       * The referral database maps a normalized DN string to labeled URIs.
176       */
177      private DN2URI dn2uri;
178    
179      /**
180       * The state database maps a config DN to config entries.
181       */
182      private State state;
183    
184      /**
185       * The set of attribute indexes.
186       */
187      private HashMap<AttributeType, AttributeIndex> attrIndexMap;
188    
189      /**
190       * The set of VLV indexes.
191       */
192      private HashMap<String, VLVIndex> vlvIndexMap;
193    
194      /**
195       * Cached value from config so they don't have to be retrieved per operation.
196       */
197      private int deadlockRetryLimit;
198    
199      private int subtreeDeleteSizeLimit;
200    
201      private int subtreeDeleteBatchSize;
202    
203      private String databasePrefix;
204      /**
205       * This class is responsible for managing the configuraiton for attribute
206       * indexes used within this entry container.
207       */
208      public class AttributeJEIndexCfgManager implements
209          ConfigurationAddListener<LocalDBIndexCfg>,
210          ConfigurationDeleteListener<LocalDBIndexCfg>
211      {
212        /**
213         * {@inheritDoc}
214         */
215        public boolean isConfigurationAddAcceptable(
216                LocalDBIndexCfg cfg,
217                List<Message> unacceptableReasons)
218        {
219          // TODO: validate more before returning true?
220          return true;
221        }
222    
223        /**
224         * {@inheritDoc}
225         */
226        public ConfigChangeResult applyConfigurationAdd(LocalDBIndexCfg cfg)
227        {
228          ConfigChangeResult ccr;
229          boolean adminActionRequired = false;
230          List<Message> messages = new ArrayList<Message>();
231    
232          try
233          {
234            AttributeIndex index =
235                new AttributeIndex(cfg, state, env, EntryContainer.this);
236            index.open();
237            attrIndexMap.put(cfg.getAttribute(), index);
238          }
239          catch(Exception e)
240          {
241            messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(e)));
242            ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
243                                         adminActionRequired,
244                                         messages);
245            return ccr;
246          }
247    
248          adminActionRequired = true;
249          messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
250                  cfg.getAttribute().getNameOrOID()));
251          return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
252                                        messages);
253        }
254    
255        /**
256         * {@inheritDoc}
257         */
258        public synchronized boolean isConfigurationDeleteAcceptable(
259            LocalDBIndexCfg cfg, List<Message> unacceptableReasons)
260        {
261          // TODO: validate more before returning true?
262          return true;
263        }
264    
265        /**
266         * {@inheritDoc}
267         */
268        public ConfigChangeResult applyConfigurationDelete(LocalDBIndexCfg cfg)
269        {
270          ConfigChangeResult ccr;
271          boolean adminActionRequired = false;
272          ArrayList<Message> messages = new ArrayList<Message>();
273    
274          exclusiveLock.lock();
275          try
276          {
277            AttributeIndex index = attrIndexMap.get(cfg.getAttribute());
278            deleteAttributeIndex(index);
279            attrIndexMap.remove(cfg.getAttribute());
280          }
281          catch(DatabaseException de)
282          {
283            messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
284            ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
285                                         adminActionRequired,
286                                         messages);
287            return ccr;
288          }
289          finally
290          {
291            exclusiveLock.unlock();
292          }
293    
294          return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
295                                        messages);
296        }
297      }
298    
299      /**
300       * This class is responsible for managing the configuraiton for VLV indexes
301       * used within this entry container.
302       */
303      public class VLVJEIndexCfgManager implements
304          ConfigurationAddListener<LocalDBVLVIndexCfg>,
305          ConfigurationDeleteListener<LocalDBVLVIndexCfg>
306      {
307        /**
308         * {@inheritDoc}
309         */
310        public boolean isConfigurationAddAcceptable(
311            LocalDBVLVIndexCfg cfg, List<Message> unacceptableReasons)
312        {
313          SearchFilter filter;
314          try
315          {
316            filter =
317                SearchFilter.createFilterFromString(cfg.getFilter());
318          }
319          catch(Exception e)
320          {
321            Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get(
322                cfg.getFilter(), cfg.getName(),
323                stackTraceToSingleLineString(e));
324            unacceptableReasons.add(msg);
325            return false;
326          }
327    
328          String[] sortAttrs = cfg.getSortOrder().split(" ");
329          SortKey[] sortKeys = new SortKey[sortAttrs.length];
330          OrderingMatchingRule[] orderingRules =
331              new OrderingMatchingRule[sortAttrs.length];
332          boolean[] ascending = new boolean[sortAttrs.length];
333          for(int i = 0; i < sortAttrs.length; i++)
334          {
335            try
336            {
337              if(sortAttrs[i].startsWith("-"))
338              {
339                ascending[i] = false;
340                sortAttrs[i] = sortAttrs[i].substring(1);
341              }
342              else
343              {
344                ascending[i] = true;
345                if(sortAttrs[i].startsWith("+"))
346                {
347                  sortAttrs[i] = sortAttrs[i].substring(1);
348                }
349              }
350            }
351            catch(Exception e)
352            {
353              Message msg =
354                  ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(
355                      String.valueOf(sortKeys[i]), cfg.getName());
356              unacceptableReasons.add(msg);
357              return false;
358            }
359    
360            AttributeType attrType =
361                DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase());
362            if(attrType == null)
363            {
364              Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(
365                  sortAttrs[i], cfg.getName());
366              unacceptableReasons.add(msg);
367              return false;
368            }
369            sortKeys[i] = new SortKey(attrType, ascending[i]);
370            orderingRules[i] = attrType.getOrderingMatchingRule();
371          }
372    
373          return true;
374        }
375    
376        /**
377         * {@inheritDoc}
378         */
379        public ConfigChangeResult applyConfigurationAdd(LocalDBVLVIndexCfg cfg)
380        {
381          ConfigChangeResult ccr;
382          boolean adminActionRequired = false;
383          ArrayList<Message> messages = new ArrayList<Message>();
384    
385          try
386          {
387            VLVIndex vlvIndex = new VLVIndex(cfg, state, env, EntryContainer.this);
388            vlvIndex.open();
389            vlvIndexMap.put(cfg.getName().toLowerCase(), vlvIndex);
390          }
391          catch(Exception e)
392          {
393            messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(e)));
394            ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
395                                         adminActionRequired,
396                                         messages);
397            return ccr;
398          }
399    
400          adminActionRequired = true;
401    
402          messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
403                  cfg.getName()));
404          return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
405                                        messages);
406        }
407    
408        /**
409         * {@inheritDoc}
410         */
411        public boolean isConfigurationDeleteAcceptable(
412                LocalDBVLVIndexCfg cfg,
413                List<Message> unacceptableReasons)
414        {
415          // TODO: validate more before returning true?
416          return true;
417        }
418    
419        /**
420         * {@inheritDoc}
421         */
422        public ConfigChangeResult applyConfigurationDelete(LocalDBVLVIndexCfg cfg)
423        {
424          ConfigChangeResult ccr;
425          boolean adminActionRequired = false;
426          List<Message> messages = new ArrayList<Message>();
427    
428          exclusiveLock.lock();
429          try
430          {
431            VLVIndex vlvIndex =
432                vlvIndexMap.get(cfg.getName().toLowerCase());
433            vlvIndex.close();
434            deleteDatabase(vlvIndex);
435            vlvIndexMap.remove(cfg.getName());
436          }
437          catch(DatabaseException de)
438          {
439            messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
440            ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
441                                         adminActionRequired,
442                                         messages);
443            return ccr;
444          }
445          finally
446          {
447            exclusiveLock.unlock();
448          }
449    
450          return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
451                                        messages);
452        }
453    
454      }
455    
456      /**
457       * A read write lock to handle schema changes and bulk changes.
458       */
459      private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
460      final Lock sharedLock = lock.readLock();
461      final Lock exclusiveLock = lock.writeLock();
462    
463      /**
464       * Create a new entry entryContainer object.
465       *
466       * @param baseDN  The baseDN this entry container will be responsible for
467       *                storing on disk.
468       * @param databasePrefix The prefix to use in the database names used by
469       *                       this entry container.
470       * @param backend A reference to the JE backend that is creating this entry
471       *                container. It is needed by the Directory Server entry cache
472       *                methods.
473       * @param config The configuration of the JE backend.
474       * @param env The JE environment to create this entryContainer in.
475       * @param rootContainer The root container this entry container is in.
476       * @throws ConfigException if a configuration related error occurs.
477       */
478      public EntryContainer(DN baseDN, String databasePrefix, Backend backend,
479                            LocalDBBackendCfg config, Environment env,
480                            RootContainer rootContainer)
481          throws ConfigException
482      {
483        this.backend = backend;
484        this.baseDN = baseDN;
485        this.config = config;
486        this.env = env;
487        this.rootContainer = rootContainer;
488    
489        StringBuilder builder = new StringBuilder(databasePrefix.length());
490        for (int i = 0; i < databasePrefix.length(); i++)
491        {
492          char ch = databasePrefix.charAt(i);
493          if (Character.isLetterOrDigit(ch))
494          {
495            builder.append(ch);
496          }
497          else
498          {
499            builder.append('_');
500          }
501        }
502        this.databasePrefix = builder.toString();
503    
504        this.deadlockRetryLimit = config.getDeadlockRetryLimit();
505        this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit();
506        this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize();
507    
508        // Instantiate the attribute indexes.
509        attrIndexMap = new HashMap<AttributeType, AttributeIndex>();
510    
511        // Instantiate the VLV indexes.
512        vlvIndexMap = new HashMap<String, VLVIndex>();
513    
514        config.addLocalDBChangeListener(this);
515    
516        attributeJEIndexCfgManager =
517            new AttributeJEIndexCfgManager();
518        config.addLocalDBIndexAddListener(attributeJEIndexCfgManager);
519        config.addLocalDBIndexDeleteListener(attributeJEIndexCfgManager);
520    
521        vlvJEIndexCfgManager =
522            new VLVJEIndexCfgManager();
523        config.addLocalDBVLVIndexAddListener(vlvJEIndexCfgManager);
524        config.addLocalDBVLVIndexDeleteListener(vlvJEIndexCfgManager);
525      }
526    
527      /**
528       * Opens the entryContainer for reading and writing.
529       *
530       * @throws DatabaseException If an error occurs in the JE database.
531       * @throws ConfigException if a configuration related error occurs.
532       */
533      public void open()
534          throws DatabaseException, ConfigException
535      {
536        try
537        {
538          DataConfig entryDataConfig =
539              new DataConfig(config.isEntriesCompressed(),
540                             config.isCompactEncoding(),
541                             rootContainer.getCompressedSchema());
542    
543          id2entry = new ID2Entry(databasePrefix + "_" + ID2ENTRY_DATABASE_NAME,
544                                  entryDataConfig, env, this);
545          id2entry.open();
546    
547          dn2id = new DN2ID(databasePrefix + "_" + DN2ID_DATABASE_NAME, env, this);
548          dn2id.open();
549    
550          state = new State(databasePrefix + "_" + STATE_DATABASE_NAME, env, this);
551          state.open();
552    
553          id2children = new Index(databasePrefix + "_" + ID2CHILDREN_DATABASE_NAME,
554                                  new ID2CIndexer(), state,
555                                  config.getIndexEntryLimit(), 0, true,
556                                  env,this);
557          id2children.open();
558          id2subtree = new Index(databasePrefix + "_" + ID2SUBTREE_DATABASE_NAME,
559                                 new ID2SIndexer(), state,
560                                 config.getIndexEntryLimit(), 0, true,
561                                 env, this);
562          id2subtree.open();
563    
564          dn2uri = new DN2URI(databasePrefix + "_" + REFERRAL_DATABASE_NAME,
565                              env, this);
566          dn2uri.open();
567    
568          for (String idx : config.listLocalDBIndexes())
569          {
570            LocalDBIndexCfg indexCfg = config.getLocalDBIndex(idx);
571    
572            //TODO: When issue 1793 is fixed, use inherited default values in
573            //admin framework instead for the entry limit.
574            AttributeIndex index =
575                new AttributeIndex(indexCfg, state, env, this);
576            index.open();
577            attrIndexMap.put(indexCfg.getAttribute(), index);
578          }
579    
580          for(String idx : config.listLocalDBVLVIndexes())
581          {
582            LocalDBVLVIndexCfg vlvIndexCfg = config.getLocalDBVLVIndex(idx);
583    
584            VLVIndex vlvIndex = new VLVIndex(vlvIndexCfg, state, env, this);
585            vlvIndex.open();
586            vlvIndexMap.put(vlvIndexCfg.getName().toLowerCase(), vlvIndex);
587          }
588        }
589        catch (DatabaseException de)
590        {
591          if (debugEnabled())
592          {
593            TRACER.debugCaught(DebugLogLevel.ERROR, de);
594          }
595          close();
596          throw de;
597        }
598      }
599    
600      /**
601       * Closes the entry entryContainer.
602       *
603       * @throws DatabaseException If an error occurs in the JE database.
604       */
605      public void close()
606          throws DatabaseException
607      {
608        // Close core indexes.
609        dn2id.close();
610        id2entry.close();
611        dn2uri.close();
612        id2children.close();
613        id2subtree.close();
614        state.close();
615    
616        // Close attribute indexes and deregister any listeners.
617        for (AttributeIndex index : attrIndexMap.values())
618        {
619          index.close();
620        }
621    
622        // Close VLV indexes and deregister any listeners.
623        for (VLVIndex vlvIndex : vlvIndexMap.values())
624        {
625          vlvIndex.close();
626        }
627    
628        config.removeLocalDBChangeListener(this);
629        config.removeLocalDBIndexAddListener(attributeJEIndexCfgManager);
630        config.removeLocalDBIndexDeleteListener(attributeJEIndexCfgManager);
631        config.removeLocalDBVLVIndexDeleteListener(vlvJEIndexCfgManager);
632        config.removeLocalDBVLVIndexDeleteListener(vlvJEIndexCfgManager);
633      }
634    
635      /**
636       * Retrieves a reference to the root container in which this entry container
637       * exists.
638       *
639       * @return  A reference to the root container in which this entry container
640       *          exists.
641       */
642      public RootContainer getRootContainer()
643      {
644        return rootContainer;
645      }
646    
647      /**
648       * Get the DN database used by this entry entryContainer. The entryContainer
649       * must have been opened.
650       *
651       * @return The DN database.
652       */
653      public DN2ID getDN2ID()
654      {
655        return dn2id;
656      }
657    
658      /**
659       * Get the entry database used by this entry entryContainer. The
660       * entryContainer must have been opened.
661       *
662       * @return The entry database.
663       */
664      public ID2Entry getID2Entry()
665      {
666        return id2entry;
667      }
668    
669      /**
670       * Get the referral database used by this entry entryContainer. The
671       * entryContainer must have been opened.
672       *
673       * @return The referral database.
674       */
675      public DN2URI getDN2URI()
676      {
677        return dn2uri;
678      }
679    
680      /**
681       * Get the children database used by this entry entryContainer.
682       * The entryContainer must have been opened.
683       *
684       * @return The children database.
685       */
686      public Index getID2Children()
687      {
688        return id2children;
689      }
690    
691      /**
692       * Get the subtree database used by this entry entryContainer.
693       * The entryContainer must have been opened.
694       *
695       * @return The subtree database.
696       */
697      public Index getID2Subtree()
698      {
699        return id2subtree;
700      }
701    
702      /**
703       * Get the state database used by this entry container.
704       * The entry container must have been opened.
705       *
706       * @return The state database.
707       */
708      public State getState()
709      {
710        return state;
711      }
712    
713      /**
714       * Look for an attribute index for the given attribute type.
715       *
716       * @param attrType The attribute type for which an attribute index is needed.
717       * @return The attribute index or null if there is none for that type.
718       */
719      public AttributeIndex getAttributeIndex(AttributeType attrType)
720      {
721        return attrIndexMap.get(attrType);
722      }
723    
724    
725      /**
726       * Return attribute index map.
727       *
728       * @return The attribute index map.
729       */
730      public Map<AttributeType, AttributeIndex> getAttributeIndexMap() {
731        return attrIndexMap;
732      }
733    
734      /**
735       * Look for an VLV index for the given index name.
736       *
737       * @param vlvIndexName The vlv index name for which an vlv index is needed.
738       * @return The VLV index or null if there is none with that name.
739       */
740      public VLVIndex getVLVIndex(String vlvIndexName)
741      {
742        return vlvIndexMap.get(vlvIndexName);
743      }
744    
745      /**
746       * Retrieve all attribute indexes.
747       *
748       * @return All attribute indexes defined in this entry container.
749       */
750      public Collection<AttributeIndex> getAttributeIndexes()
751      {
752        return attrIndexMap.values();
753      }
754    
755      /**
756       * Retrieve all VLV indexes.
757       *
758       * @return The collection of VLV indexes defined in this entry container.
759       */
760      public Collection<VLVIndex> getVLVIndexes()
761      {
762        return vlvIndexMap.values();
763      }
764    
765      /**
766       * Determine the highest entryID in the entryContainer.
767       * The entryContainer must already be open.
768       *
769       * @return The highest entry ID.
770       * @throws DatabaseException If an error occurs in the JE database.
771       */
772      public EntryID getHighestEntryID() throws DatabaseException
773      {
774        EntryID entryID = new EntryID(0);
775        Cursor cursor = id2entry.openCursor(null, null);
776        DatabaseEntry key = new DatabaseEntry();
777        DatabaseEntry data = new DatabaseEntry();
778    
779        // Position a cursor on the last data item, and the key should
780        // give the highest ID.
781        try
782        {
783          OperationStatus status = cursor.getLast(key, data, LockMode.DEFAULT);
784          if (status == OperationStatus.SUCCESS)
785          {
786            entryID = new EntryID(key);
787          }
788        }
789        finally
790        {
791          cursor.close();
792        }
793        return entryID;
794      }
795    
796      /**
797       * Determine the number of subordinate entries for a given entry.
798       *
799       * @param entryDN The distinguished name of the entry.
800       * @param subtree <code>true</code> will include all the entries under the
801       *                given entries. <code>false</code> will only return the
802       *                number of entries immediately under the given entry.
803       * @return The number of subordinate entries for the given entry or -1 if
804       *         the entry does not exist.
805       * @throws DatabaseException If an error occurs in the JE database.
806       */
807      public long getNumSubordinates(DN entryDN, boolean subtree)
808          throws DatabaseException
809      {
810        EntryID entryID = dn2id.get(null, entryDN, LockMode.DEFAULT);
811        if (entryID != null)
812        {
813          DatabaseEntry key =
814              new DatabaseEntry(JebFormat.entryIDToDatabase(entryID.longValue()));
815          EntryIDSet entryIDSet;
816          if(!subtree)
817          {
818            entryIDSet = id2children.readKey(key, null, LockMode.DEFAULT);
819          }
820          else
821          {
822            entryIDSet = id2subtree.readKey(key, null, LockMode.DEFAULT);
823          }
824          long count = entryIDSet.size();
825          if(count != Long.MAX_VALUE)
826          {
827            return count;
828          }
829        }
830        return -1;
831      }
832    
833      /**
834       * Processes the specified search in this entryContainer.
835       * Matching entries should be provided back to the core server using the
836       * <CODE>SearchOperation.returnEntry</CODE> method.
837       *
838       * @param searchOperation The search operation to be processed.
839       * @throws org.opends.server.types.DirectoryException
840       *          If a problem occurs while processing the
841       *          search.
842       * @throws DatabaseException If an error occurs in the JE database.
843       * @throws JebException If an error occurs in the JE database.
844       */
845      public void search(SearchOperation searchOperation)
846           throws DirectoryException, DatabaseException, JebException
847      {
848        DN baseDN = searchOperation.getBaseDN();
849        SearchScope searchScope = searchOperation.getScope();
850    
851        List<Control> controls = searchOperation.getRequestControls();
852        PagedResultsControl pageRequest = null;
853        ServerSideSortRequestControl sortRequest = null;
854        VLVRequestControl vlvRequest = null;
855        if (controls != null)
856        {
857          for (Control control : controls)
858          {
859            if (control.getOID().equals(OID_PAGED_RESULTS_CONTROL))
860            {
861              // Ignore all but the first paged results control.
862              if (pageRequest == null)
863              {
864                try
865                {
866                  pageRequest = new PagedResultsControl(control.isCritical(),
867                                                        control.getValue());
868                }
869                catch (LDAPException e)
870                {
871                  if (debugEnabled())
872                  {
873                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
874                  }
875                  throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
876                                               e.getMessageObject(), e);
877                }
878    
879                if (vlvRequest != null)
880                {
881                  Message message =
882                      ERR_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV.get();
883                  throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
884                                               message);
885                }
886              }
887            }
888            else if (control.getOID().equals(OID_SERVER_SIDE_SORT_REQUEST_CONTROL))
889            {
890              // Ignore all but the first sort request control.
891              if (sortRequest == null)
892              {
893                try
894                {
895                  sortRequest = ServerSideSortRequestControl.decodeControl(control);
896                }
897                catch (LDAPException e)
898                {
899                  if (debugEnabled())
900                  {
901                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
902                  }
903                  throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
904                                               e.getMessageObject(), e);
905                }
906              }
907            }
908            else if (control.getOID().equals(OID_VLV_REQUEST_CONTROL))
909            {
910              // Ignore all but the first VLV request control.
911              if (vlvRequest == null)
912              {
913                try
914                {
915                  vlvRequest = VLVRequestControl.decodeControl(control);
916                }
917                catch (LDAPException e)
918                {
919                  if (debugEnabled())
920                  {
921                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
922                  }
923                  throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
924                                               e.getMessageObject(), e);
925                }
926    
927                if (pageRequest != null)
928                {
929                  Message message =
930                      ERR_JEB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV.get();
931                  throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
932                                               message);
933                }
934              }
935            }
936          }
937        }
938    
939        // Handle client abandon of paged results.
940        if (pageRequest != null)
941        {
942          if (pageRequest.getSize() == 0)
943          {
944            PagedResultsControl control;
945            control = new PagedResultsControl(pageRequest.isCritical(), 0,
946                                              new ASN1OctetString());
947            searchOperation.getResponseControls().add(control);
948            return;
949          }
950        }
951    
952        // Handle base-object search first.
953        if (searchScope == SearchScope.BASE_OBJECT)
954        {
955          // Fetch the base entry.
956          Entry baseEntry = null;
957          try
958          {
959            baseEntry = getEntry(baseDN);
960          }
961          catch (Exception e)
962          {
963            if (debugEnabled())
964            {
965              TRACER.debugCaught(DebugLogLevel.ERROR, e);
966            }
967          }
968    
969          // The base entry must exist for a successful result.
970          if (baseEntry == null)
971          {
972            // Check for referral entries above the base entry.
973            dn2uri.targetEntryReferrals(baseDN, searchScope);
974    
975            Message message = ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
976            DN matchedDN = getMatchedDN(baseDN);
977            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
978                message, matchedDN, null);
979          }
980    
981          if (!isManageDsaITOperation(searchOperation))
982          {
983            dn2uri.checkTargetForReferral(baseEntry, searchOperation.getScope());
984          }
985    
986          if (searchOperation.getFilter().matchesEntry(baseEntry))
987          {
988            searchOperation.returnEntry(baseEntry, null);
989          }
990    
991          if (pageRequest != null)
992          {
993            // Indicate no more pages.
994            PagedResultsControl control;
995            control = new PagedResultsControl(pageRequest.isCritical(), 0,
996                                              new ASN1OctetString());
997            searchOperation.getResponseControls().add(control);
998          }
999    
1000          return;
1001        }
1002    
1003        // Check whether the client requested debug information about the
1004        // contribution of the indexes to the search.
1005        StringBuilder debugBuffer = null;
1006        if (searchOperation.getAttributes().contains(ATTR_DEBUG_SEARCH_INDEX))
1007        {
1008          debugBuffer = new StringBuilder();
1009        }
1010    
1011        EntryIDSet entryIDList = null;
1012        boolean candidatesAreInScope = false;
1013        if(sortRequest != null)
1014        {
1015          for(VLVIndex vlvIndex : vlvIndexMap.values())
1016          {
1017            try
1018            {
1019              entryIDList =
1020                  vlvIndex.evaluate(null, searchOperation, sortRequest, vlvRequest,
1021                                    debugBuffer);
1022              if(entryIDList != null)
1023              {
1024                searchOperation.addResponseControl(
1025                    new ServerSideSortResponseControl(LDAPResultCode.SUCCESS,
1026                                                      null));
1027                candidatesAreInScope = true;
1028                break;
1029              }
1030            }
1031            catch (DirectoryException de)
1032            {
1033              searchOperation.addResponseControl(
1034                  new ServerSideSortResponseControl(
1035                      de.getResultCode().getIntValue(), null));
1036    
1037              if (sortRequest.isCritical())
1038              {
1039                throw de;
1040              }
1041            }
1042          }
1043        }
1044    
1045        if(entryIDList == null)
1046        {
1047          // Create an index filter to get the search result candidate entries.
1048          IndexFilter indexFilter =
1049              new IndexFilter(this, searchOperation, debugBuffer);
1050    
1051          // Evaluate the filter against the attribute indexes.
1052          entryIDList = indexFilter.evaluate();
1053    
1054          // Evaluate the search scope against the id2children and id2subtree
1055          // indexes.
1056          if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD)
1057          {
1058            // Read the ID from dn2id.
1059            EntryID baseID = dn2id.get(null, baseDN, LockMode.DEFAULT);
1060            if (baseID == null)
1061            {
1062              Message message =
1063                      ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
1064              DN matchedDN = getMatchedDN(baseDN);
1065              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
1066                  message, matchedDN, null);
1067            }
1068            DatabaseEntry baseIDData = baseID.getDatabaseEntry();
1069    
1070            EntryIDSet scopeList;
1071            if (searchScope == SearchScope.SINGLE_LEVEL)
1072            {
1073              scopeList = id2children.readKey(baseIDData, null, LockMode.DEFAULT);
1074            }
1075            else
1076            {
1077              scopeList = id2subtree.readKey(baseIDData, null, LockMode.DEFAULT);
1078              if (searchScope == SearchScope.WHOLE_SUBTREE)
1079              {
1080                // The id2subtree list does not include the base entry ID.
1081                scopeList.add(baseID);
1082              }
1083            }
1084            entryIDList.retainAll(scopeList);
1085            if (debugBuffer != null)
1086            {
1087              debugBuffer.append(" scope=");
1088              debugBuffer.append(searchScope);
1089              scopeList.toString(debugBuffer);
1090            }
1091            if (scopeList.isDefined())
1092            {
1093              // In this case we know that every candidate is in scope.
1094              candidatesAreInScope = true;
1095            }
1096          }
1097    
1098          if (sortRequest != null)
1099          {
1100            try
1101            {
1102              entryIDList = EntryIDSetSorter.sort(this, entryIDList,
1103                                                  searchOperation,
1104                                                  sortRequest.getSortOrder(),
1105                                                  vlvRequest);
1106              searchOperation.addResponseControl(
1107                  new ServerSideSortResponseControl(LDAPResultCode.SUCCESS, null));
1108            }
1109            catch (DirectoryException de)
1110            {
1111              searchOperation.addResponseControl(
1112                  new ServerSideSortResponseControl(
1113                      de.getResultCode().getIntValue(), null));
1114    
1115              if (sortRequest.isCritical())
1116              {
1117                throw de;
1118              }
1119            }
1120          }
1121        }
1122    
1123        // If requested, construct and return a fictitious entry containing
1124        // debug information, and no other entries.
1125        if (debugBuffer != null)
1126        {
1127          debugBuffer.append(" final=");
1128          entryIDList.toString(debugBuffer);
1129    
1130          AttributeSyntax syntax =
1131               DirectoryServer.getDefaultStringSyntax();
1132          AttributeType attrType =
1133               DirectoryServer.getDefaultAttributeType(ATTR_DEBUG_SEARCH_INDEX,
1134                                                       syntax);
1135          ASN1OctetString valueString =
1136               new ASN1OctetString(debugBuffer.toString());
1137          LinkedHashSet<AttributeValue> values =
1138               new LinkedHashSet<AttributeValue>();
1139          values.add(new AttributeValue(valueString, valueString));
1140          Attribute attr = new Attribute(attrType, ATTR_DEBUG_SEARCH_INDEX, values);
1141    
1142          Entry debugEntry;
1143          debugEntry = new Entry(DN.decode("cn=debugsearch"), null, null, null);
1144          debugEntry.addAttribute(attr, new ArrayList<AttributeValue>());
1145    
1146          searchOperation.returnEntry(debugEntry, null);
1147          return;
1148        }
1149    
1150        if (entryIDList.isDefined())
1151        {
1152          searchIndexed(entryIDList, candidatesAreInScope, searchOperation,
1153                        pageRequest);
1154        }
1155        else
1156        {
1157          // See if we could use a virtual attribute rule to process the search.
1158          for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes())
1159          {
1160            if (rule.getProvider().isSearchable(rule, searchOperation))
1161            {
1162              rule.getProvider().processSearch(rule, searchOperation);
1163              return;
1164            }
1165          }
1166    
1167          ClientConnection clientConnection =
1168              searchOperation.getClientConnection();
1169          if(! clientConnection.hasPrivilege(Privilege.UNINDEXED_SEARCH,
1170                                             searchOperation))
1171          {
1172            Message message =
1173                ERR_JEB_SEARCH_UNINDEXED_INSUFFICIENT_PRIVILEGES.get();
1174            throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1175                                         message);
1176          }
1177    
1178          if (sortRequest != null)
1179          {
1180            // FIXME -- Add support for sorting unindexed searches using indexes
1181            //          like DSEE currently does.
1182            searchOperation.addResponseControl(
1183                 new ServerSideSortResponseControl(
1184                          LDAPResultCode.UNWILLING_TO_PERFORM, null));
1185    
1186            if (sortRequest.isCritical())
1187            {
1188              Message message = ERR_JEB_SEARCH_CANNOT_SORT_UNINDEXED.get();
1189              throw new DirectoryException(
1190                             ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, message);
1191            }
1192          }
1193    
1194          searchNotIndexed(searchOperation, pageRequest);
1195        }
1196      }
1197    
1198      /**
1199       * We were not able to obtain a set of candidate entry IDs for the
1200       * search from the indexes.
1201       * <p>
1202       * Here we are relying on the DN key order to ensure children are
1203       * returned after their parents.
1204       * <ul>
1205       * <li>iterate through a subtree range of the DN database
1206       * <li>discard non-children DNs if the search scope is single level
1207       * <li>fetch the entry by ID from the entry cache or the entry database
1208       * <li>return the entry if it matches the filter
1209       * </ul>
1210       *
1211       * @param searchOperation The search operation.
1212       * @param pageRequest A Paged Results control, or null if none.
1213       * @throws DirectoryException If an error prevented the search from being
1214       * processed.
1215       */
1216      private void searchNotIndexed(SearchOperation searchOperation,
1217                                    PagedResultsControl pageRequest)
1218           throws DirectoryException
1219      {
1220        EntryCache<?> entryCache = DirectoryServer.getEntryCache();
1221        DN baseDN = searchOperation.getBaseDN();
1222        SearchScope searchScope = searchOperation.getScope();
1223        boolean manageDsaIT = isManageDsaITOperation(searchOperation);
1224    
1225        // The base entry must already have been processed if this is
1226        // a request for the next page in paged results.  So we skip
1227        // the base entry processing if the cookie is set.
1228        if (pageRequest == null || pageRequest.getCookie().value().length == 0)
1229        {
1230          // Fetch the base entry.
1231          Entry baseEntry = null;
1232          try
1233          {
1234            baseEntry = getEntry(baseDN);
1235          }
1236          catch (Exception e)
1237          {
1238            if (debugEnabled())
1239            {
1240              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1241            }
1242          }
1243    
1244          // The base entry must exist for a successful result.
1245          if (baseEntry == null)
1246          {
1247            // Check for referral entries above the base entry.
1248            dn2uri.targetEntryReferrals(baseDN, searchScope);
1249    
1250            Message message = ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
1251            DN matchedDN = getMatchedDN(baseDN);
1252            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
1253                message, matchedDN, null);
1254          }
1255    
1256          if (!manageDsaIT)
1257          {
1258            dn2uri.checkTargetForReferral(baseEntry, searchScope);
1259          }
1260    
1261          /*
1262           * The base entry is only included for whole subtree search.
1263           */
1264          if (searchScope == SearchScope.WHOLE_SUBTREE)
1265          {
1266            if (searchOperation.getFilter().matchesEntry(baseEntry))
1267            {
1268              searchOperation.returnEntry(baseEntry, null);
1269            }
1270          }
1271    
1272          if (!manageDsaIT)
1273          {
1274            // Return any search result references.
1275            if (!dn2uri.returnSearchReferences(searchOperation))
1276            {
1277              if (pageRequest != null)
1278              {
1279                // Indicate no more pages.
1280                PagedResultsControl control;
1281                control = new PagedResultsControl(pageRequest.isCritical(), 0,
1282                                                  new ASN1OctetString());
1283                searchOperation.getResponseControls().add(control);
1284              }
1285            }
1286          }
1287        }
1288    
1289        /*
1290         * We will iterate forwards through a range of the dn2id keys to
1291         * find subordinates of the target entry from the top of the tree
1292         * downwards. For example, any subordinates of "dc=example,dc=com" appear
1293         * in dn2id with a key ending in ",dc=example,dc=com". The entry
1294         * "cn=joe,ou=people,dc=example,dc=com" will appear after the entry
1295         * "ou=people,dc=example,dc=com".
1296         */
1297        byte[] suffix = StaticUtils.getBytes("," + baseDN.toNormalizedString());
1298    
1299        /*
1300         * Set the ending value to a value of equal length but slightly
1301         * greater than the suffix. Since keys are compared in
1302         * reverse order we must set the first byte (the comma).
1303         * No possibility of overflow here.
1304         */
1305        byte[] end = suffix.clone();
1306        end[0] = (byte) (end[0] + 1);
1307    
1308        // Set the starting value.
1309        byte[] begin;
1310        if (pageRequest != null && pageRequest.getCookie().value().length != 0)
1311        {
1312          // The cookie contains the DN of the next entry to be returned.
1313          try
1314          {
1315            DN lastDN = DN.decode(pageRequest.getCookie());
1316            begin = StaticUtils.getBytes(lastDN.toNormalizedString());
1317          }
1318          catch (Exception e)
1319          {
1320            if (debugEnabled())
1321            {
1322              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1323            }
1324            String str = StaticUtils.bytesToHex(pageRequest.getCookie().value());
1325            Message msg = ERR_JEB_INVALID_PAGED_RESULTS_COOKIE.get(str);
1326            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1327                                         msg, e);
1328          }
1329        }
1330        else
1331        {
1332          // Set the starting value to the suffix.
1333          begin = suffix;
1334        }
1335    
1336        DatabaseEntry data = new DatabaseEntry();
1337        DatabaseEntry key = new DatabaseEntry(begin);
1338        List<Lock> lockList = new ArrayList<Lock>(1);
1339    
1340        int lookthroughCount = 0;
1341        int lookthroughLimit =
1342            searchOperation.getClientConnection().getLookthroughLimit();
1343    
1344        try
1345        {
1346          Cursor cursor = dn2id.openCursor(null, null);
1347          try
1348          {
1349            OperationStatus status;
1350    
1351            // Initialize the cursor very close to the starting value.
1352            status = cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
1353    
1354            // Step forward until we pass the ending value.
1355            while (status == OperationStatus.SUCCESS)
1356            {
1357              if(lookthroughLimit > 0 && lookthroughCount > lookthroughLimit)
1358              {
1359                //Lookthrough limit exceeded
1360                searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
1361                searchOperation.appendErrorMessage(
1362                  NOTE_JEB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit));
1363                return;
1364              }
1365              int cmp = dn2id.getComparator().compare(key.getData(), end);
1366              if (cmp >= 0)
1367              {
1368                // We have gone past the ending value.
1369                break;
1370              }
1371    
1372              // We have found a subordinate entry.
1373    
1374              EntryID entryID = new EntryID(data);
1375              DN dn = DN.decode(new ASN1OctetString(key.getData()));
1376    
1377              boolean isInScope = true;
1378              if (searchScope == SearchScope.SINGLE_LEVEL)
1379              {
1380                // Check if this entry is an immediate child.
1381                if ((dn.getNumComponents() !=
1382                     baseDN.getNumComponents() + 1))
1383                {
1384                  isInScope = false;
1385                }
1386              }
1387    
1388              if (isInScope)
1389              {
1390                Entry entry = null;
1391                Entry cacheEntry = null;
1392    
1393                // Try the entry cache first. Note no need to take a lock.
1394                lockList.clear();
1395                cacheEntry = entryCache.getEntry(backend, entryID.longValue(),
1396                                                 LockType.NONE, lockList);
1397    
1398                if (cacheEntry == null)
1399                {
1400                  GetEntryByIDOperation operation =
1401                       new GetEntryByIDOperation(entryID);
1402    
1403                  // Fetch the candidate entry from the database.
1404                  this.invokeTransactedOperation(operation);
1405                  entry = operation.getEntry();
1406                }
1407                else
1408                {
1409                  entry = cacheEntry;
1410                }
1411    
1412                // Process the candidate entry.
1413                if (entry != null)
1414                {
1415                  lookthroughCount++;
1416    
1417                  if (manageDsaIT || entry.getReferralURLs() == null)
1418                  {
1419                    // Filter the entry.
1420                    if (searchOperation.getFilter().matchesEntry(entry))
1421                    {
1422                      if (pageRequest != null &&
1423                           searchOperation.getEntriesSent() ==
1424                           pageRequest.getSize())
1425                      {
1426                        // The current page is full.
1427                        // Set the cookie to remember where we were.
1428                        ASN1OctetString cookie = new ASN1OctetString(key.getData());
1429                        PagedResultsControl control;
1430                        control = new PagedResultsControl(pageRequest.isCritical(),
1431                                                          0, cookie);
1432                        searchOperation.getResponseControls().add(control);
1433                        return;
1434                      }
1435    
1436                      if (!searchOperation.returnEntry(entry, null))
1437                      {
1438                        // We have been told to discontinue processing of the
1439                        // search. This could be due to size limit exceeded or
1440                        // operation cancelled.
1441                        return;
1442                      }
1443                    }
1444                  }
1445                }
1446              }
1447    
1448              // Move to the next record.
1449              status = cursor.getNext(key, data, LockMode.DEFAULT);
1450            }
1451          }
1452          finally
1453          {
1454            cursor.close();
1455          }
1456        }
1457        catch (DatabaseException e)
1458        {
1459          if (debugEnabled())
1460          {
1461            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1462          }
1463        }
1464        catch (JebException e)
1465        {
1466          if (debugEnabled())
1467          {
1468            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1469          }
1470        }
1471    
1472        if (pageRequest != null)
1473        {
1474          // Indicate no more pages.
1475          PagedResultsControl control;
1476          control = new PagedResultsControl(pageRequest.isCritical(), 0,
1477                                            new ASN1OctetString());
1478          searchOperation.getResponseControls().add(control);
1479        }
1480    
1481      }
1482    
1483      /**
1484       * We were able to obtain a set of candidate entry IDs for the
1485       * search from the indexes.
1486       * <p>
1487       * Here we are relying on ID order to ensure children are returned
1488       * after their parents.
1489       * <ul>
1490       * <li>Iterate through the candidate IDs
1491       * <li>fetch entry by ID from cache or id2entry
1492       * <li>put the entry in the cache if not present
1493       * <li>discard entries that are not in scope
1494       * <li>return entry if it matches the filter
1495       * </ul>
1496       *
1497       * @param entryIDList The candidate entry IDs.
1498       * @param candidatesAreInScope true if it is certain that every candidate
1499       *                             entry is in the search scope.
1500       * @param searchOperation The search operation.
1501       * @param pageRequest A Paged Results control, or null if none.
1502       * @throws DirectoryException If an error prevented the search from being
1503       * processed.
1504       */
1505      private void searchIndexed(EntryIDSet entryIDList,
1506                                 boolean candidatesAreInScope,
1507                                 SearchOperation searchOperation,
1508                                 PagedResultsControl pageRequest)
1509           throws DirectoryException
1510      {
1511        EntryCache<?> entryCache = DirectoryServer.getEntryCache();
1512        SearchScope searchScope = searchOperation.getScope();
1513        DN baseDN = searchOperation.getBaseDN();
1514        boolean manageDsaIT = isManageDsaITOperation(searchOperation);
1515        boolean continueSearch = true;
1516    
1517        // Set the starting value.
1518        EntryID begin = null;
1519        if (pageRequest != null && pageRequest.getCookie().value().length != 0)
1520        {
1521          // The cookie contains the ID of the next entry to be returned.
1522          try
1523          {
1524            begin = new EntryID(new DatabaseEntry(pageRequest.getCookie().value()));
1525          }
1526          catch (Exception e)
1527          {
1528            if (debugEnabled())
1529            {
1530              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1531            }
1532            String str = StaticUtils.bytesToHex(pageRequest.getCookie().value());
1533            Message msg = ERR_JEB_INVALID_PAGED_RESULTS_COOKIE.get(str);
1534            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1535                                         msg, e);
1536          }
1537        }
1538        else
1539        {
1540          if (!manageDsaIT)
1541          {
1542            // Return any search result references.
1543            continueSearch = dn2uri.returnSearchReferences(searchOperation);
1544          }
1545        }
1546    
1547        // Make sure the candidate list is smaller than the lookthrough limit
1548        int lookthroughLimit =
1549            searchOperation.getClientConnection().getLookthroughLimit();
1550        if(lookthroughLimit > 0 && entryIDList.size() > lookthroughLimit)
1551        {
1552          //Lookthrough limit exceeded
1553          searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
1554          searchOperation.appendErrorMessage(
1555              NOTE_JEB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit));
1556          continueSearch = false;
1557        }
1558    
1559        // Iterate through the index candidates.
1560        if (continueSearch)
1561        {
1562          List<Lock> lockList = new ArrayList<Lock>();
1563          Iterator<EntryID> iterator = entryIDList.iterator(begin);
1564          while (iterator.hasNext())
1565          {
1566            EntryID id = iterator.next();
1567            Entry entry = null;
1568            Entry cacheEntry = null;
1569    
1570            // Try the entry cache first. Note no need to take a lock.
1571            lockList.clear();
1572            cacheEntry = entryCache.getEntry(backend, id.longValue(),
1573                                             LockType.NONE, lockList);
1574    
1575            // Release any entry lock whatever happens during this block.
1576            // (This is actually redundant since we did not take a lock).
1577            try
1578            {
1579              if (cacheEntry == null)
1580              {
1581                GetEntryByIDOperation operation = new GetEntryByIDOperation(id);
1582    
1583                // Fetch the candidate entry from the database.
1584                try
1585                {
1586                  this.invokeTransactedOperation(operation);
1587                  entry = operation.getEntry();
1588                }
1589                catch (Exception e)
1590                {
1591                  if (debugEnabled())
1592                  {
1593                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
1594                  }
1595                  continue;
1596                }
1597              }
1598              else
1599              {
1600                entry = cacheEntry;
1601              }
1602    
1603              // Process the candidate entry.
1604              if (entry != null)
1605              {
1606                boolean isInScope = false;
1607                DN entryDN = entry.getDN();
1608    
1609                if (candidatesAreInScope)
1610                {
1611                  isInScope = true;
1612                }
1613                else if (searchScope == SearchScope.SINGLE_LEVEL)
1614                {
1615                  // Check if this entry is an immediate child.
1616                  if ((entryDN.getNumComponents() ==
1617                       baseDN.getNumComponents() + 1) &&
1618                       entryDN.isDescendantOf(baseDN))
1619                  {
1620                    isInScope = true;
1621                  }
1622                }
1623                else if (searchScope == SearchScope.WHOLE_SUBTREE)
1624                {
1625                  if (entryDN.isDescendantOf(baseDN))
1626                  {
1627                    isInScope = true;
1628                  }
1629                }
1630                else if (searchScope == SearchScope.SUBORDINATE_SUBTREE)
1631                {
1632                  if ((entryDN.getNumComponents() >
1633                       baseDN.getNumComponents()) &&
1634                       entryDN.isDescendantOf(baseDN))
1635                  {
1636                    isInScope = true;
1637                  }
1638                }
1639    
1640                // Put this entry in the cache if it did not come from the cache.
1641                if (cacheEntry == null)
1642                {
1643                  // Put the entry in the cache making sure not to overwrite
1644                  // a newer copy that may have been inserted since the time
1645                  // we read the cache.
1646                  entryCache.putEntryIfAbsent(entry, backend, id.longValue());
1647                }
1648    
1649                // Filter the entry if it is in scope.
1650                if (isInScope)
1651                {
1652                  if (manageDsaIT || entry.getReferralURLs() == null)
1653                  {
1654                    if (searchOperation.getFilter().matchesEntry(entry))
1655                    {
1656                      if (pageRequest != null &&
1657                           searchOperation.getEntriesSent() ==
1658                           pageRequest.getSize())
1659                      {
1660                        // The current page is full.
1661                        // Set the cookie to remember where we were.
1662                        byte[] cookieBytes = id.getDatabaseEntry().getData();
1663                        ASN1OctetString cookie = new ASN1OctetString(cookieBytes);
1664                        PagedResultsControl control;
1665                        control = new PagedResultsControl(pageRequest.isCritical(),
1666                                                          0, cookie);
1667                        searchOperation.getResponseControls().add(control);
1668                        return;
1669                      }
1670    
1671                      if (!searchOperation.returnEntry(entry, null))
1672                      {
1673                        // We have been told to discontinue processing of the
1674                        // search. This could be due to size limit exceeded or
1675                        // operation cancelled.
1676                        break;
1677                      }
1678                    }
1679                  }
1680                }
1681              }
1682            }
1683            finally
1684            {
1685              // Release any entry lock acquired by the entry cache
1686              // (This is actually redundant since we did not take a lock).
1687              for (Lock lock : lockList)
1688              {
1689                lock.unlock();
1690              }
1691            }
1692          }
1693        }
1694    
1695        // Before we return success from the search we must ensure the base entry
1696        // exists. However, if we have returned at least one entry or subordinate
1697        // reference it implies the base does exist, so we can omit the check.
1698        if (searchOperation.getEntriesSent() == 0 &&
1699             searchOperation.getReferencesSent() == 0)
1700        {
1701          // Fetch the base entry if it exists.
1702          Entry baseEntry = null;
1703          try
1704          {
1705            baseEntry = getEntry(baseDN);
1706          }
1707          catch (Exception e)
1708          {
1709            if (debugEnabled())
1710            {
1711              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1712            }
1713          }
1714    
1715          // The base entry must exist for a successful result.
1716          if (baseEntry == null)
1717          {
1718            // Check for referral entries above the base entry.
1719            dn2uri.targetEntryReferrals(baseDN, searchScope);
1720    
1721            Message message = ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
1722            DN matchedDN = getMatchedDN(baseDN);
1723            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
1724                message, matchedDN, null);
1725          }
1726    
1727          if (!manageDsaIT)
1728          {
1729            dn2uri.checkTargetForReferral(baseEntry, searchScope);
1730          }
1731        }
1732    
1733        if (pageRequest != null)
1734        {
1735          // Indicate no more pages.
1736          PagedResultsControl control;
1737          control = new PagedResultsControl(pageRequest.isCritical(), 0,
1738                                            new ASN1OctetString());
1739          searchOperation.getResponseControls().add(control);
1740        }
1741    
1742      }
1743    
1744      /**
1745       * Adds the provided entry to this database.  This method must ensure that the
1746       * entry is appropriate for the database and that no entry already exists with
1747       * the same DN.  The caller must hold a write lock on the DN of the provided
1748       * entry.
1749       *
1750       * @param entry        The entry to add to this database.
1751       * @param addOperation The add operation with which the new entry is
1752       *                     associated.  This may be <CODE>null</CODE> for adds
1753       *                     performed internally.
1754       * @throws DirectoryException If a problem occurs while trying to add the
1755       *                            entry.
1756       * @throws DatabaseException If an error occurs in the JE database.
1757       * @throws JebException If an error occurs in the JE backend.
1758       */
1759      public void addEntry(Entry entry, AddOperation addOperation)
1760          throws DatabaseException, DirectoryException, JebException
1761      {
1762        TransactedOperation operation =
1763            new AddEntryTransaction(entry);
1764    
1765        invokeTransactedOperation(operation);
1766      }
1767    
1768      /**
1769       * This method is common to all operations invoked under a database
1770       * transaction. It retries the operation if the transaction is
1771       * aborted due to a deadlock condition, up to a configured maximum
1772       * number of retries.
1773       *
1774       * @param operation An object implementing the TransactedOperation interface.
1775       * @throws DatabaseException If an error occurs in the JE database.
1776       * @throws DirectoryException If a Directory Server error occurs.
1777       * @throws JebException If an error occurs in the JE backend.
1778       */
1779      private void invokeTransactedOperation(TransactedOperation operation)
1780          throws DatabaseException, DirectoryException, JebException
1781      {
1782        // Attempt the operation under a transaction until it fails or completes.
1783        boolean completed = false;
1784        int retryRemaining = deadlockRetryLimit;
1785        while (!completed)
1786        {
1787          // Start a transaction.
1788          Transaction txn = operation.beginOperationTransaction();
1789    
1790          try
1791          {
1792            // Invoke the operation.
1793            operation.invokeOperation(txn);
1794    
1795            // Commit the transaction.
1796            EntryContainer.transactionCommit(txn);
1797            completed = true;
1798          }
1799          catch (DeadlockException deadlockException)
1800          {
1801            EntryContainer.transactionAbort(txn);
1802            if (retryRemaining-- <= 0)
1803            {
1804              throw deadlockException;
1805            }
1806            if (debugEnabled())
1807            {
1808              TRACER.debugCaught(DebugLogLevel.ERROR, deadlockException);
1809            }
1810          }
1811          catch (DatabaseException databaseException)
1812          {
1813            EntryContainer.transactionAbort(txn);
1814            throw databaseException;
1815          }
1816          catch (DirectoryException directoryException)
1817          {
1818            EntryContainer.transactionAbort(txn);
1819            throw directoryException;
1820          }
1821          catch (JebException jebException)
1822          {
1823            EntryContainer.transactionAbort(txn);
1824            throw jebException;
1825          }
1826          catch (Exception e)
1827          {
1828            EntryContainer.transactionAbort(txn);
1829    
1830            Message message = ERR_JEB_UNCHECKED_EXCEPTION.get();
1831            throw new JebException(message, e);
1832          }
1833        }
1834    
1835        // Do any actions necessary after successful commit,
1836        // usually to update the entry cache.
1837        operation.postCommitAction();
1838      }
1839    
1840      /**
1841       * This interface represents any kind of operation on the database
1842       * that must be performed under a transaction. A class which implements
1843       * this interface does not need to be concerned with creating the
1844       * transaction nor retrying the transaction after deadlock.
1845       */
1846      private interface TransactedOperation
1847      {
1848        /**
1849         * Begin a transaction for this operation.
1850         *
1851         * @return The transaction for the operation, or null if the operation
1852         *         will not use a transaction.
1853         * @throws DatabaseException If an error occurs in the JE database.
1854         */
1855        public abstract Transaction beginOperationTransaction()
1856            throws DatabaseException;
1857    
1858        /**
1859         * Invoke the operation under the given transaction.
1860         *
1861         * @param txn The transaction to be used to perform the operation.
1862         * @throws DatabaseException If an error occurs in the JE database.
1863         * @throws DirectoryException If a Directory Server error occurs.
1864         * @throws JebException If an error occurs in the JE backend.
1865         */
1866        public abstract void invokeOperation(Transaction txn)
1867            throws DatabaseException, DirectoryException, JebException;
1868    
1869        /**
1870         * This method is called after the transaction has successfully
1871         * committed.
1872         */
1873        public abstract void postCommitAction();
1874      }
1875    
1876      /**
1877       * This inner class implements the Add Entry operation through
1878       * the TransactedOperation interface.
1879       */
1880      private class AddEntryTransaction implements TransactedOperation
1881      {
1882        /**
1883         * The entry to be added.
1884         */
1885        private Entry entry;
1886    
1887        /**
1888         * The DN of the superior entry of the entry to be added.  This can be
1889         * null if the entry to be added is a base entry.
1890         */
1891        DN parentDN;
1892    
1893        /**
1894         * The ID of the entry once it has been assigned.
1895         */
1896        EntryID entryID = null;
1897    
1898        /**
1899         * Begin a transaction for this operation.
1900         *
1901         * @return The transaction for the operation, or null if the operation
1902         *         will not use a transaction.
1903         * @throws DatabaseException If an error occurs in the JE database.
1904         */
1905        public Transaction beginOperationTransaction() throws DatabaseException
1906        {
1907          Transaction txn =  beginTransaction();
1908          return txn;
1909        }
1910    
1911        /**
1912         * Create a new Add Entry Transaction.
1913         * @param entry The entry to be added.
1914         */
1915        public AddEntryTransaction(Entry entry)
1916        {
1917          this.entry = entry;
1918          this.parentDN = getParentWithinBase(entry.getDN());
1919        }
1920    
1921        /**
1922         * Invoke the operation under the given transaction.
1923         *
1924         * @param txn The transaction to be used to perform the operation.
1925         * @throws DatabaseException If an error occurs in the JE database.
1926         * @throws DirectoryException If a Directory Server error occurs.
1927         * @throws JebException If an error occurs in the JE backend.
1928         */
1929        public void invokeOperation(Transaction txn)
1930            throws DatabaseException, DirectoryException, JebException
1931        {
1932          // Check whether the entry already exists.
1933          if (dn2id.get(txn, entry.getDN(), LockMode.DEFAULT) != null)
1934          {
1935            Message message =
1936                ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString());
1937            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
1938                                         message);
1939          }
1940    
1941          // Check that the parent entry exists.
1942          EntryID parentID = null;
1943          if (parentDN != null)
1944          {
1945            // Check for referral entries above the target.
1946            dn2uri.targetEntryReferrals(entry.getDN(), null);
1947    
1948            // Read the parent ID from dn2id.
1949            parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT);
1950            if (parentID == null)
1951            {
1952              Message message = ERR_JEB_ADD_NO_SUCH_OBJECT.get(
1953                      entry.getDN().toString());
1954              DN matchedDN = getMatchedDN(baseDN);
1955              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
1956                  message, matchedDN, null);
1957            }
1958          }
1959    
1960          // First time through, assign the next entryID.
1961          if (entryID == null)
1962          {
1963            entryID = rootContainer.getNextEntryID();
1964          }
1965    
1966          // Insert into dn2id.
1967          if (!dn2id.insert(txn, entry.getDN(), entryID))
1968          {
1969            // Do not ever expect to come through here.
1970            Message message =
1971                ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString());
1972            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
1973                                         message);
1974          }
1975    
1976          // Update the referral database for referral entries.
1977          if (!dn2uri.addEntry(txn, entry))
1978          {
1979            // Do not ever expect to come through here.
1980            Message message =
1981                ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString());
1982            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
1983                                         message);
1984          }
1985    
1986          // Insert into id2entry.
1987          if (!id2entry.insert(txn, entryID, entry))
1988          {
1989            // Do not ever expect to come through here.
1990            Message message =
1991                ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString());
1992            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
1993                                         message);
1994          }
1995    
1996          // Insert into the indexes, in index configuration order.
1997          indexInsertEntry(txn, entry, entryID);
1998    
1999          // Insert into id2children and id2subtree.
2000          // The database transaction locks on these records will be hotly
2001          // contested so we do them last so as to hold the locks for the
2002          // shortest duration.
2003          if (parentDN != null)
2004          {
2005            // Insert into id2children for parent ID.
2006            id2children.insertID(txn, parentID.getDatabaseEntry(), entryID);
2007    
2008            // Insert into id2subtree for parent ID.
2009            id2subtree.insertID(txn, parentID.getDatabaseEntry(), entryID);
2010    
2011            // Iterate up through the superior entries, starting above the parent.
2012            for (DN dn = getParentWithinBase(parentDN); dn != null;
2013                 dn = getParentWithinBase(dn))
2014            {
2015              // Read the ID from dn2id.
2016              EntryID nodeID = dn2id.get(txn, dn, LockMode.DEFAULT);
2017              if (nodeID == null)
2018              {
2019                Message msg =
2020                    ERR_JEB_MISSING_DN2ID_RECORD.get(dn.toNormalizedString());
2021                throw new JebException(msg);
2022              }
2023    
2024              // Insert into id2subtree for this node.
2025              id2subtree.insertID(txn, nodeID.getDatabaseEntry(), entryID);
2026            }
2027          }
2028    
2029        }
2030    
2031        /**
2032         * This method is called after the transaction has successfully
2033         * committed.
2034         */
2035        public void postCommitAction()
2036        {
2037          // Update the entry cache.
2038          EntryCache entryCache = DirectoryServer.getEntryCache();
2039          if (entryCache != null)
2040          {
2041            entryCache.putEntry(entry, backend, entryID.longValue());
2042          }
2043        }
2044      }
2045    
2046      /**
2047       * Removes the specified entry from this database.  This method must ensure
2048       * that the entry exists and that it does not have any subordinate entries
2049       * (unless the database supports a subtree delete operation and the client
2050       * included the appropriate information in the request).  The caller must hold
2051       * a write lock on the provided entry DN.
2052       *
2053       * @param entryDN         The DN of the entry to remove from this database.
2054       * @param deleteOperation The delete operation with which this action is
2055       *                        associated.  This may be <CODE>null</CODE> for
2056       *                        deletes performed internally.
2057       * @throws DirectoryException If a problem occurs while trying to remove the
2058       *                            entry.
2059       * @throws DatabaseException If an error occurs in the JE database.
2060       * @throws JebException If an error occurs in the JE backend.
2061       */
2062      public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
2063          throws DirectoryException, DatabaseException, JebException
2064      {
2065        DeleteEntryTransaction operation =
2066            new DeleteEntryTransaction(entryDN, deleteOperation);
2067        boolean isComplete = false;
2068        while(!isComplete)
2069        {
2070          invokeTransactedOperation(operation);
2071    
2072          if (operation.adminSizeLimitExceeded())
2073          {
2074            Message message = NOTE_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED.get(
2075                    operation.getDeletedEntryCount());
2076            throw new DirectoryException(
2077              ResultCode.ADMIN_LIMIT_EXCEEDED,
2078              message);
2079          }
2080          if(operation.batchSizeExceeded())
2081          {
2082            operation.resetBatchSize();
2083            continue;
2084          }
2085          isComplete = true;
2086          Message message =
2087              NOTE_JEB_DELETED_ENTRY_COUNT.get(operation.getDeletedEntryCount());
2088          MessageBuilder errorMessage = new MessageBuilder();
2089          errorMessage.append(message);
2090          deleteOperation.setErrorMessage(errorMessage);
2091        }
2092      }
2093    
2094      /**
2095       * This inner class implements the Delete Entry operation through
2096       * the TransactedOperation interface.
2097       */
2098      private class DeleteEntryTransaction implements TransactedOperation
2099      {
2100        /**
2101         * The DN of the entry or subtree to be deleted.
2102         */
2103        private DN entryDN;
2104    
2105        /**
2106         * The Delete operation.
2107         */
2108        private DeleteOperation deleteOperation;
2109    
2110    
2111        /**
2112         * Indicates whether the subtree delete size limit has been exceeded.
2113         */
2114        private boolean adminSizeLimitExceeded = false;
2115    
2116    
2117        /**
2118         * Indicates whether the subtree delete batch size has been exceeded.
2119         */
2120        private boolean batchSizeExceeded = false;
2121    
2122    
2123        /**
2124         * Indicates the total count of deleted DNs in the Delete Operation.
2125         */
2126        private int totalDeletedDN;
2127    
2128        /**
2129         * Indicates the batch count of deleted DNs in the Delete Operation.
2130         */
2131        private int batchDeletedDN;
2132    
2133        /**
2134         * The index buffer used to buffer up the index changes.
2135         */
2136        private IndexBuffer indexBuffer = null;
2137    
2138        /**
2139         * Create a new Delete Entry Transaction.
2140         * @param entryDN The entry or subtree to be deleted.
2141         * @param deleteOperation The Delete operation.
2142         */
2143        public DeleteEntryTransaction(DN entryDN, DeleteOperation deleteOperation)
2144        {
2145          this.entryDN = entryDN;
2146          this.deleteOperation = deleteOperation;
2147        }
2148    
2149        /**
2150         * Determine whether the subtree delete size limit has been exceeded.
2151         * @return true if the size limit has been exceeded.
2152         */
2153        public boolean adminSizeLimitExceeded()
2154        {
2155          return adminSizeLimitExceeded;
2156        }
2157    
2158        /**
2159         * Determine whether the subtree delete batch size has been exceeded.
2160         * @return true if the batch size has been exceeded.
2161         */
2162        public boolean batchSizeExceeded()
2163        {
2164          return batchSizeExceeded;
2165        }
2166    
2167        /**
2168         * Resets the batchSizeExceeded parameter to reuse the object
2169         * for multiple batches.
2170         */
2171        public void resetBatchSize()
2172        {
2173          batchSizeExceeded=false;
2174          batchDeletedDN = 0;
2175        }
2176    
2177        /**
2178         * Get the number of entries deleted during the operation.
2179         * @return The number of entries deleted.
2180         */
2181        public int getDeletedEntryCount()
2182        {
2183          return totalDeletedDN;
2184        }
2185    
2186        /**
2187         * Begin a transaction for this operation.
2188         *
2189         * @return The transaction for the operation, or null if the operation
2190         *         will not use a transaction.
2191         * @throws DatabaseException If an error occurs in the JE database.
2192         */
2193        public Transaction beginOperationTransaction() throws DatabaseException
2194        {
2195          Transaction txn =  beginTransaction();
2196          return txn;
2197        }
2198    
2199        /**
2200         * Invoke the operation under the given transaction.
2201         *
2202         * @param txn The transaction to be used to perform the operation.
2203         * @throws DatabaseException If an error occurs in the JE database.
2204         * @throws DirectoryException If a Directory Server error occurs.
2205         * @throws JebException If an error occurs in the JE backend.
2206         */
2207        public void invokeOperation(Transaction txn)
2208            throws DatabaseException, DirectoryException, JebException
2209        {
2210          // Check for referral entries above the target entry.
2211          dn2uri.targetEntryReferrals(entryDN, null);
2212    
2213          // Determine whether this is a subtree delete.
2214          boolean isSubtreeDelete = false;
2215          List<Control> controls = deleteOperation.getRequestControls();
2216          if (controls != null)
2217          {
2218            for (Control control : controls)
2219            {
2220              if (control.getOID().equals(OID_SUBTREE_DELETE_CONTROL))
2221              {
2222                isSubtreeDelete = true;
2223              }
2224            }
2225          }
2226    
2227          /*
2228           * We will iterate backwards through a range of the dn2id keys to
2229           * find subordinates of the target entry from the bottom of the tree
2230           * upwards. For example, any subordinates of "dc=example,dc=com" appear
2231           * in dn2id with a key ending in ",dc=example,dc=com". The entry
2232           * "cn=joe,ou=people,dc=example,dc=com" will appear after the entry
2233           * "ou=people,dc=example,dc=com".
2234           */
2235          byte[] suffix = StaticUtils.getBytes("," + entryDN.toNormalizedString());
2236    
2237          /*
2238           * Set the starting value to a value of equal length but slightly
2239           * greater than the target DN. Since keys are compared in
2240           * reverse order we must set the first byte (the comma).
2241           * No possibility of overflow here.
2242           */
2243          byte[] begin = suffix.clone();
2244          begin[0] = (byte) (begin[0] + 1);
2245    
2246          // Set the ending value to the suffix.
2247          byte[] end = suffix;
2248    
2249          DatabaseEntry data = new DatabaseEntry();
2250          DatabaseEntry key = new DatabaseEntry(begin);
2251          CursorConfig cursorConfig = new CursorConfig();
2252          cursorConfig.setReadCommitted(true);
2253    
2254          Cursor cursor = dn2id.openCursor(txn, cursorConfig);
2255          try
2256          {
2257            OperationStatus status;
2258    
2259            // Initialize the cursor very close to the starting value.
2260            status = cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
2261            if (status == OperationStatus.NOTFOUND)
2262            {
2263              status = cursor.getLast(key, data, LockMode.DEFAULT);
2264            }
2265    
2266            // Step back until the key is less than the beginning value
2267            while (status == OperationStatus.SUCCESS &&
2268                dn2id.getComparator().compare(key.getData(), begin) >= 0)
2269            {
2270              status = cursor.getPrev(key, data, LockMode.DEFAULT);
2271            }
2272    
2273            // Step back until we pass the ending value.
2274            while (status == OperationStatus.SUCCESS)
2275            {
2276              int cmp = dn2id.getComparator().compare(key.getData(), end);
2277              if (cmp < 0)
2278              {
2279                // We have gone past the ending value.
2280                break;
2281              }
2282    
2283              // We have found a subordinate entry.
2284    
2285              if (!isSubtreeDelete)
2286              {
2287                // The subtree delete control was not specified and
2288                // the target entry is not a leaf.
2289                Message message =
2290                    ERR_JEB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(entryDN.toString());
2291                throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF,
2292                                             message);
2293              }
2294    
2295              // Enforce any subtree delete size limit.
2296              if (subtreeDeleteSizeLimit > 0 &&
2297                  totalDeletedDN >= subtreeDeleteSizeLimit)
2298              {
2299                adminSizeLimitExceeded = true;
2300                break;
2301              }
2302    
2303              // Enforce any subtree delete batch size.
2304              if (subtreeDeleteBatchSize > 0 &&
2305                  batchDeletedDN >= subtreeDeleteBatchSize)
2306              {
2307                batchSizeExceeded = true;
2308                break;
2309              }
2310    
2311              // This is a subtree delete so crate a index buffer
2312              // if it there isn't one.
2313              if(indexBuffer == null)
2314              {
2315                indexBuffer = new IndexBuffer(EntryContainer.this);
2316              }
2317    
2318              /*
2319               * Delete this entry which by now must be a leaf because
2320               * we have been deleting from the bottom of the tree upwards.
2321               */
2322              EntryID entryID = new EntryID(data);
2323              DN subordinateDN = DN.decode(new ASN1OctetString(key.getData()));
2324              deleteEntry(txn, true, entryDN, subordinateDN, entryID);
2325    
2326              batchDeletedDN++;
2327              totalDeletedDN++;
2328              status = cursor.getPrev(key, data, LockMode.DEFAULT);
2329            }
2330          }
2331          finally
2332          {
2333            cursor.close();
2334          }
2335    
2336          // Finally delete the target entry as it was not included
2337          // in the dn2id iteration.
2338          if (!adminSizeLimitExceeded && !batchSizeExceeded)
2339          {
2340            // Enforce any subtree delete size limit.
2341            if (subtreeDeleteSizeLimit > 0 &&
2342                totalDeletedDN >= subtreeDeleteSizeLimit)
2343            {
2344              adminSizeLimitExceeded = true;
2345            }
2346            else if (subtreeDeleteBatchSize > 0 &&
2347                batchDeletedDN >= subtreeDeleteBatchSize)
2348            {
2349              batchSizeExceeded = true;
2350            }
2351            else
2352            {
2353              // draft-armijo-ldap-treedelete, 4.1 Tree Delete Semantics:
2354              // The server MUST NOT chase referrals stored in the tree.  If
2355              // information about referrals is stored in this section of the
2356              // tree, this pointer will be deleted.
2357              deleteEntry(txn,
2358                  isSubtreeDelete || isManageDsaITOperation(deleteOperation),
2359                  entryDN, null, null);
2360    
2361              batchDeletedDN++;
2362              totalDeletedDN++;
2363            }
2364          }
2365    
2366          if(indexBuffer != null)
2367          {
2368            indexBuffer.flush(txn);
2369          }
2370        }
2371    
2372        /**
2373         * Delete an entry with appropriate handling of referral entries.
2374         * The caller must be sure that the entry is indeed a leaf. We cannot
2375         * rely on id2children to check for children since this entry may at
2376         * one time have had enough children to exceed the index entry limit,
2377         * after which the number of children IDs is unknown.
2378         *
2379         * @param txn    The database transaction.
2380         * @param manageDsaIT Whether it is an manage DSA IT operation.
2381         * @param targetDN The DN of the target entry.
2382         * @param leafDN The DN of the leaf entry to be deleted.
2383         * @param leafID The ID of the leaf entry.
2384         * @throws DatabaseException If an error occurs in the JE database.
2385         * @throws DirectoryException If a Directory Server error occurs.
2386         * @throws JebException If an error occurs in the JE backend.
2387         */
2388        private void deleteEntry(Transaction txn,
2389                                 boolean manageDsaIT,
2390                                 DN targetDN,
2391                                 DN leafDN,
2392                                 EntryID leafID)
2393            throws DatabaseException, DirectoryException, JebException
2394        {
2395          if(leafID == null || leafDN == null)
2396          {
2397            // Read the entry ID from dn2id.
2398            leafDN = targetDN;
2399            leafID = dn2id.get(txn, leafDN, LockMode.RMW);
2400            if (leafID == null)
2401            {
2402              Message message =
2403                  ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString());
2404              DN matchedDN = getMatchedDN(baseDN);
2405              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
2406                  message, matchedDN, null);
2407            }
2408          }
2409    
2410          // Remove from dn2id.
2411          if (!dn2id.remove(txn, leafDN))
2412          {
2413            // Do not expect to ever come through here.
2414            Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString());
2415            DN matchedDN = getMatchedDN(baseDN);
2416            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
2417                message, matchedDN, null);
2418          }
2419    
2420          // Check that the entry exists in id2entry and read its contents.
2421          Entry entry = id2entry.get(txn, leafID, LockMode.RMW);
2422          if (entry == null)
2423          {
2424            Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString());
2425            throw new JebException(msg);
2426          }
2427    
2428          if (!manageDsaIT)
2429          {
2430            dn2uri.checkTargetForReferral(entry, null);
2431          }
2432    
2433          // Update the referral database.
2434          dn2uri.deleteEntry(txn, entry);
2435    
2436          // Remove from id2entry.
2437          if (!id2entry.remove(txn, leafID))
2438          {
2439            Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString());
2440            throw new JebException(msg);
2441          }
2442    
2443          // Remove from the indexes, in index config order.
2444          if(indexBuffer != null)
2445          {
2446           indexRemoveEntry(indexBuffer, entry, leafID);
2447          }
2448          else
2449          {
2450            indexRemoveEntry(txn, entry, leafID);
2451          }
2452    
2453          // Remove the id2c and id2s records for this entry.
2454          if(indexBuffer != null)
2455          {
2456            byte[] leafIDKeyBytes =
2457                JebFormat.entryIDToDatabase(leafID.longValue());
2458            id2children.delete(indexBuffer, leafIDKeyBytes);
2459            id2subtree.delete(indexBuffer, leafIDKeyBytes);
2460          }
2461          else
2462          {
2463            DatabaseEntry leafIDKey = leafID.getDatabaseEntry();
2464            id2children.delete(txn, leafIDKey);
2465            id2subtree.delete(txn, leafIDKey);
2466          }
2467    
2468          // Iterate up through the superior entries from the target entry.
2469          boolean isParent = true;
2470          for (DN parentDN = getParentWithinBase(targetDN); parentDN != null;
2471               parentDN = getParentWithinBase(parentDN))
2472          {
2473            // Read the ID from dn2id.
2474            EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT);
2475            if (parentID == null)
2476            {
2477              Message msg =
2478                  ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN.toNormalizedString());
2479              throw new JebException(msg);
2480            }
2481    
2482            if(indexBuffer != null)
2483            {
2484              byte[] parentIDBytes =
2485                  JebFormat.entryIDToDatabase(parentID.longValue());
2486              // Remove from id2children.
2487              if (isParent)
2488              {
2489                id2children.removeID(indexBuffer, parentIDBytes, leafID);
2490                isParent = false;
2491              }
2492              id2subtree.removeID(indexBuffer, parentIDBytes, leafID);
2493            }
2494            else
2495            {
2496              DatabaseEntry nodeIDData = parentID.getDatabaseEntry();
2497              // Remove from id2children.
2498              if(isParent)
2499              {
2500                id2children.removeID(txn, nodeIDData, leafID);
2501                isParent = false;
2502              }
2503              id2subtree.removeID(txn, nodeIDData, leafID);
2504            }
2505          }
2506    
2507          // Remove the entry from the entry cache.
2508          EntryCache entryCache = DirectoryServer.getEntryCache();
2509          if (entryCache != null)
2510          {
2511            entryCache.removeEntry(leafDN);
2512          }
2513        }
2514    
2515        /**
2516         * This method is called after the transaction has successfully
2517         * committed.
2518         */
2519        public void postCommitAction()
2520        {
2521    
2522        }
2523      }
2524    
2525      /**
2526       * Indicates whether an entry with the specified DN exists.
2527       *
2528       * @param  entryDN  The DN of the entry for which to determine existence.
2529       *
2530       * @return  <CODE>true</CODE> if the specified entry exists,
2531       *          or <CODE>false</CODE> if it does not.
2532       *
2533       * @throws  DirectoryException  If a problem occurs while trying to make the
2534       *                              determination.
2535       */
2536      public boolean entryExists(DN entryDN)
2537          throws DirectoryException
2538      {
2539        EntryCache entryCache = DirectoryServer.getEntryCache();
2540    
2541        // Try the entry cache first.
2542        if (entryCache != null)
2543        {
2544          if (entryCache.containsEntry(entryDN))
2545          {
2546            return true;
2547          }
2548        }
2549    
2550        // Read the ID from dn2id.
2551        EntryID id = null;
2552        try
2553        {
2554          id = dn2id.get(null, entryDN, LockMode.DEFAULT);
2555        }
2556        catch (DatabaseException e)
2557        {
2558          if (debugEnabled())
2559          {
2560            TRACER.debugCaught(DebugLogLevel.ERROR, e);
2561          }
2562        }
2563    
2564        return id != null;
2565      }
2566    
2567      /**
2568       * Fetch an entry by DN, trying the entry cache first, then the database.
2569       * Retrieves the requested entry, trying the entry cache first,
2570       * then the database.  Note that the caller must hold a read or write lock
2571       * on the specified DN.
2572       *
2573       * @param entryDN The distinguished name of the entry to retrieve.
2574       * @return The requested entry, or <CODE>null</CODE> if the entry does not
2575       *         exist.
2576       * @throws DirectoryException If a problem occurs while trying to retrieve
2577       *                            the entry.
2578       * @throws JebException If an error occurs in the JE backend.
2579       * @throws DatabaseException An error occurred during a database operation.
2580       */
2581      public Entry getEntry(DN entryDN)
2582          throws JebException, DatabaseException, DirectoryException
2583      {
2584        EntryCache entryCache = DirectoryServer.getEntryCache();
2585        Entry entry = null;
2586    
2587        // Try the entry cache first.
2588        if (entryCache != null)
2589        {
2590          entry = entryCache.getEntry(entryDN);
2591        }
2592    
2593        if (entry == null)
2594        {
2595          GetEntryByDNOperation operation = new GetEntryByDNOperation(entryDN);
2596    
2597          // Fetch the entry from the database.
2598          invokeTransactedOperation(operation);
2599    
2600          entry = operation.getEntry();
2601    
2602          // Put the entry in the cache making sure not to overwrite
2603          // a newer copy that may have been inserted since the time
2604          // we read the cache.
2605          if (entry != null && entryCache != null)
2606          {
2607            entryCache.putEntryIfAbsent(entry, backend,
2608                                        operation.getEntryID().longValue());
2609          }
2610        }
2611    
2612        return entry;
2613      }
2614    
2615      /**
2616       * This inner class gets an entry by DN through
2617       * the TransactedOperation interface.
2618       */
2619      private class GetEntryByDNOperation implements TransactedOperation
2620      {
2621        /**
2622         * The retrieved entry.
2623         */
2624        private Entry entry = null;
2625    
2626        /**
2627         * The ID of the retrieved entry.
2628         */
2629        private EntryID entryID = null;
2630    
2631        /**
2632         * The DN of the entry to be retrieved.
2633         */
2634        DN entryDN;
2635    
2636        /**
2637         * Create a new transacted operation to retrieve an entry by DN.
2638         * @param entryDN The DN of the entry to be retrieved.
2639         */
2640        public GetEntryByDNOperation(DN entryDN)
2641        {
2642          this.entryDN = entryDN;
2643        }
2644    
2645        /**
2646         * Get the retrieved entry.
2647         * @return The retrieved entry.
2648         */
2649        public Entry getEntry()
2650        {
2651          return entry;
2652        }
2653    
2654        /**
2655         * Get the ID of the retrieved entry.
2656         * @return The ID of the retrieved entry.
2657         */
2658        public EntryID getEntryID()
2659        {
2660          return entryID;
2661        }
2662    
2663        /**
2664         * Begin a transaction for this operation.
2665         *
2666         * @return The transaction for the operation, or null if the operation
2667         *         will not use a transaction.
2668         * @throws DatabaseException If an error occurs in the JE database.
2669         */
2670        public Transaction beginOperationTransaction() throws DatabaseException
2671        {
2672          // For best performance queries do not use a transaction.
2673          // We permit temporary inconsistencies between the multiple
2674          // records that make up a single entry.
2675          return null;
2676        }
2677    
2678        /**
2679         * Invoke the operation under the given transaction.
2680         *
2681         * @param txn The transaction to be used to perform the operation
2682         * @throws DatabaseException If an error occurs in the JE database.
2683         * @throws DirectoryException If a Directory Server error occurs.
2684         * @throws JebException If an error occurs in the JE backend.
2685         */
2686        public void invokeOperation(Transaction txn) throws DatabaseException,
2687                                                            DirectoryException,
2688                                                            JebException
2689        {
2690          // Read dn2id.
2691          entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT);
2692          if (entryID == null)
2693          {
2694            // The entryDN does not exist.
2695    
2696            // Check for referral entries above the target entry.
2697            dn2uri.targetEntryReferrals(entryDN, null);
2698    
2699            return;
2700          }
2701    
2702          // Read id2entry.
2703          entry = id2entry.get(txn, entryID, LockMode.DEFAULT);
2704    
2705          if (entry == null)
2706          {
2707            // The entryID does not exist.
2708            Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(entryID.toString());
2709            throw new JebException(msg);
2710          }
2711    
2712        }
2713    
2714        /**
2715         * This method is called after the transaction has successfully
2716         * committed.
2717         */
2718        public void postCommitAction()
2719        {
2720          // No implementation required.
2721        }
2722      }
2723    
2724      /**
2725       * This inner class gets an entry by ID through
2726       * the TransactedOperation interface.
2727       */
2728      private class GetEntryByIDOperation implements TransactedOperation
2729      {
2730        /**
2731         * The retrieved entry.
2732         */
2733        private Entry entry = null;
2734    
2735        /**
2736         * The ID of the entry to be retrieved.
2737         */
2738        private EntryID entryID;
2739    
2740        /**
2741         * Create a new transacted operation to retrieve an entry by ID.
2742         * @param entryID The ID of the entry to be retrieved.
2743         */
2744        public GetEntryByIDOperation(EntryID entryID)
2745        {
2746          this.entryID = entryID;
2747        }
2748    
2749        /**
2750         * Get the retrieved entry.
2751         * @return The retrieved entry.
2752         */
2753        public Entry getEntry()
2754        {
2755          return entry;
2756        }
2757    
2758        /**
2759         * Get the ID of the retrieved entry.
2760         * @return the ID of the retrieved entry.
2761         */
2762        public EntryID getEntryID()
2763        {
2764          return entryID;
2765        }
2766    
2767        /**
2768         * Begin a transaction for this operation.
2769         *
2770         * @return The transaction for the operation, or null if the operation
2771         *         will not use a transaction.
2772         * @throws DatabaseException If an error occurs in the JE database.
2773         */
2774        public Transaction beginOperationTransaction() throws DatabaseException
2775        {
2776          // For best performance queries do not use a transaction.
2777          // We permit temporary inconsistencies between the multiple
2778          // records that make up a single entry.
2779          return null;
2780        }
2781    
2782        /**
2783         * Invoke the operation under the given transaction.
2784         *
2785         * @param txn The transaction to be used to perform the operation.
2786         * @throws DatabaseException If an error occurs in the JE database.
2787         * @throws DirectoryException If a Directory Server error occurs.
2788         * @throws JebException If an error occurs in the JE backend.
2789         */
2790        public void invokeOperation(Transaction txn) throws DatabaseException,
2791                                                            DirectoryException,
2792                                                            JebException
2793        {
2794          // Read id2entry.
2795          entry = id2entry.get(txn, entryID, LockMode.DEFAULT);
2796        }
2797    
2798        /**
2799         * This method is called after the transaction has successfully
2800         * committed.
2801         */
2802        public void postCommitAction()
2803        {
2804          // No implementation required.
2805        }
2806      }
2807    
2808      /**
2809       * The simplest case of replacing an entry in which the entry DN has
2810       * not changed.
2811       *
2812       * @param entry           The new contents of the entry
2813       * @param modifyOperation The modify operation with which this action is
2814       *                        associated.  This may be <CODE>null</CODE> for
2815       *                        modifications performed internally.
2816       * @throws DatabaseException If an error occurs in the JE database.
2817       * @throws DirectoryException If a Directory Server error occurs.
2818       * @throws JebException If an error occurs in the JE backend.
2819       */
2820      public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
2821           throws DatabaseException, DirectoryException, JebException
2822      {
2823        TransactedOperation operation =
2824             new ReplaceEntryTransaction(entry, modifyOperation);
2825    
2826        invokeTransactedOperation(operation);
2827      }
2828    
2829      /**
2830       * This inner class implements the Replace Entry operation through
2831       * the TransactedOperation interface.
2832       */
2833      private class ReplaceEntryTransaction implements TransactedOperation
2834      {
2835        /**
2836         * The new contents of the entry.
2837         */
2838        private Entry entry;
2839    
2840        /**
2841         * The Modify operation, or null if the replace is not due to a Modify
2842         * operation.
2843         */
2844        private ModifyOperation modifyOperation;
2845    
2846        /**
2847         * The ID of the entry that was replaced.
2848         */
2849        private EntryID entryID = null;
2850    
2851        /**
2852         * Create a new transacted operation to replace an entry.
2853         * @param entry The new contents of the entry.
2854         * @param modifyOperation The Modify operation, or null if the replace is
2855         * not due to a Modify operation.
2856         */
2857        public ReplaceEntryTransaction(Entry entry,
2858                                       ModifyOperation modifyOperation)
2859        {
2860          this.entry = entry;
2861          this.modifyOperation = modifyOperation;
2862        }
2863    
2864        /**
2865         * Begin a transaction for this operation.
2866         *
2867         * @return The transaction for the operation, or null if the operation
2868         *         will not use a transaction.
2869         * @throws DatabaseException If an error occurs in the JE database.
2870         */
2871        public Transaction beginOperationTransaction() throws DatabaseException
2872        {
2873          Transaction txn =  beginTransaction();
2874          return txn;
2875        }
2876    
2877        /**
2878         * Invoke the operation under the given transaction.
2879         *
2880         * @param txn The transaction to be used to perform the operation.
2881         * @throws DatabaseException If an error occurs in the JE database.
2882         * @throws DirectoryException If a Directory Server error occurs.
2883         * @throws JebException If an error occurs in the JE backend.
2884         */
2885        public void invokeOperation(Transaction txn) throws DatabaseException,
2886                                                            DirectoryException,
2887                                                            JebException
2888        {
2889          // Read dn2id.
2890          entryID = dn2id.get(txn, entry.getDN(), LockMode.RMW);
2891          if (entryID == null)
2892          {
2893            // The entry does not exist.
2894            Message message =
2895                    ERR_JEB_MODIFY_NO_SUCH_OBJECT.get(entry.getDN().toString());
2896            DN matchedDN = getMatchedDN(baseDN);
2897            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
2898                message, matchedDN, null);
2899          }
2900    
2901          // Read id2entry for the original entry.
2902          Entry originalEntry = id2entry.get(txn, entryID, LockMode.RMW);
2903          if (originalEntry == null)
2904          {
2905            // The entry does not exist.
2906            Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(entryID.toString());
2907            throw new JebException(msg);
2908          }
2909    
2910          if (!isManageDsaITOperation(modifyOperation))
2911          {
2912            // Check if the entry is a referral entry.
2913            dn2uri.checkTargetForReferral(originalEntry, null);
2914          }
2915    
2916          // Update the referral database.
2917          if (modifyOperation != null)
2918          {
2919            // In this case we know from the operation what the modifications were.
2920            List<Modification> mods = modifyOperation.getModifications();
2921            dn2uri.modifyEntry(txn, originalEntry, entry, mods);
2922          }
2923          else
2924          {
2925            dn2uri.replaceEntry(txn, originalEntry, entry);
2926          }
2927    
2928          // Replace id2entry.
2929          id2entry.put(txn, entryID, entry);
2930    
2931          // Update the indexes.
2932          if (modifyOperation != null)
2933          {
2934            // In this case we know from the operation what the modifications were.
2935            List<Modification> mods = modifyOperation.getModifications();
2936            indexModifications(txn, originalEntry, entry, entryID, mods);
2937          }
2938          else
2939          {
2940            // The most optimal would be to figure out what the modifications were.
2941            indexRemoveEntry(txn, originalEntry, entryID);
2942            indexInsertEntry(txn, entry, entryID);
2943          }
2944        }
2945    
2946        /**
2947         * This method is called after the transaction has successfully
2948         * committed.
2949         */
2950        public void postCommitAction()
2951        {
2952          // Update the entry cache.
2953          EntryCache entryCache = DirectoryServer.getEntryCache();
2954          if (entryCache != null)
2955          {
2956            entryCache.putEntry(entry, backend, entryID.longValue());
2957          }
2958        }
2959      }
2960    
2961      /**
2962       * Moves and/or renames the provided entry in this backend, altering any
2963       * subordinate entries as necessary.  This must ensure that an entry already
2964       * exists with the provided current DN, and that no entry exists with the
2965       * target DN of the provided entry.  The caller must hold write locks on both
2966       * the current DN and the new DN for the entry.
2967       *
2968       * @param currentDN         The current DN of the entry to be replaced.
2969       * @param entry             The new content to use for the entry.
2970       * @param modifyDNOperation The modify DN operation with which this action
2971       *                          is associated.  This may be <CODE>null</CODE>
2972       *                          for modify DN operations performed internally.
2973       * @throws org.opends.server.types.DirectoryException
2974       *          If a problem occurs while trying to perform
2975       *          the rename.
2976       * @throws org.opends.server.types.CanceledOperationException
2977       *          If this backend noticed and reacted
2978       *          to a request to cancel or abandon the
2979       *          modify DN operation.
2980       * @throws DatabaseException If an error occurs in the JE database.
2981       * @throws JebException If an error occurs in the JE backend.
2982       */
2983      public void renameEntry(DN currentDN, Entry entry,
2984                              ModifyDNOperation modifyDNOperation)
2985          throws DatabaseException, JebException, DirectoryException,
2986          CanceledOperationException {
2987        TransactedOperation operation =
2988            new RenameEntryTransaction(currentDN, entry, modifyDNOperation);
2989    
2990        invokeTransactedOperation(operation);
2991      }
2992    
2993      /**
2994       * This inner class implements the Modify DN operation through
2995       * the TransactedOperation interface.
2996       */
2997      private class RenameEntryTransaction implements TransactedOperation
2998      {
2999        /**
3000         * The DN of the entry to be renamed.
3001         */
3002        private DN oldApexDN;
3003    
3004        /**
3005         * The DN of the superior entry of the entry to be renamed.
3006         * This is null if the entry to be renamed is a base entry.
3007         */
3008        private DN oldSuperiorDN;
3009    
3010        /**
3011         * The DN of the new superior entry, which can be the same
3012         * as the current superior entry.
3013         */
3014        private DN newSuperiorDN;
3015    
3016        /**
3017         * The new contents of the entry to be renamed.
3018         */
3019        private Entry newApexEntry;
3020    
3021        /**
3022         * The Modify DN operation.
3023         */
3024        private ModifyDNOperation modifyDNOperation;
3025    
3026        /**
3027         * Whether the apex entry moved under another parent.
3028         */
3029        private boolean isApexEntryMoved;
3030    
3031        /**
3032         * Create a new transacted operation for a Modify DN operation.
3033         * @param currentDN The DN of the entry to be renamed.
3034         * @param entry The new contents of the entry.
3035         * @param modifyDNOperation The Modify DN operation to be performed.
3036         */
3037        public RenameEntryTransaction(DN currentDN, Entry entry,
3038                                      ModifyDNOperation modifyDNOperation)
3039        {
3040          this.oldApexDN = currentDN;
3041          this.oldSuperiorDN = getParentWithinBase(currentDN);
3042          this.newSuperiorDN = getParentWithinBase(entry.getDN());
3043          this.newApexEntry = entry;
3044          this.modifyDNOperation = modifyDNOperation;
3045    
3046          if(oldSuperiorDN != null)
3047          {
3048            this.isApexEntryMoved = ! oldSuperiorDN.equals(newSuperiorDN);
3049          }
3050          else if(newSuperiorDN != null)
3051          {
3052            this.isApexEntryMoved = ! newSuperiorDN.equals(oldSuperiorDN);
3053          }
3054          else
3055          {
3056            this.isApexEntryMoved = false;
3057          }
3058        }
3059    
3060        /**
3061         * Invoke the operation under the given transaction.
3062         *
3063         * @param txn The transaction to be used to perform the operation.
3064         * @throws DatabaseException If an error occurs in the JE database.
3065         * @throws DirectoryException If a Directory Server error occurs.
3066         * @throws JebException If an error occurs in the JE backend.
3067         */
3068        public void invokeOperation(Transaction txn)
3069            throws DatabaseException, DirectoryException, JebException
3070        {
3071          IndexBuffer buffer = new IndexBuffer(EntryContainer.this);
3072    
3073          // Check whether the renamed entry already exists.
3074          if (dn2id.get(txn, newApexEntry.getDN(), LockMode.DEFAULT) != null)
3075          {
3076            Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(
3077                newApexEntry.getDN().toString());
3078            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
3079                                         message);
3080          }
3081    
3082          EntryID oldApexID = dn2id.get(txn, oldApexDN, LockMode.DEFAULT);
3083          if (oldApexID == null)
3084          {
3085            // Check for referral entries above the target entry.
3086            dn2uri.targetEntryReferrals(oldApexDN, null);
3087    
3088            Message message =
3089                    ERR_JEB_MODIFYDN_NO_SUCH_OBJECT.get(oldApexDN.toString());
3090            DN matchedDN = getMatchedDN(baseDN);
3091            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
3092                message, matchedDN, null);
3093          }
3094    
3095          Entry oldApexEntry = id2entry.get(txn, oldApexID, LockMode.DEFAULT);
3096          if (oldApexEntry == null)
3097          {
3098            Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(oldApexID.toString());
3099            throw new JebException(msg);
3100          }
3101    
3102          if (!isManageDsaITOperation(modifyDNOperation))
3103          {
3104            dn2uri.checkTargetForReferral(oldApexEntry, null);
3105          }
3106    
3107          EntryID newApexID = oldApexID;
3108          if (newSuperiorDN != null && isApexEntryMoved)
3109          {
3110            /*
3111             * We want to preserve the invariant that the ID of an
3112             * entry is greater than its parent, since search
3113             * results are returned in ID order.
3114             */
3115            EntryID newSuperiorID = dn2id.get(txn, newSuperiorDN, LockMode.DEFAULT);
3116            if (newSuperiorID == null)
3117            {
3118              Message msg =
3119                      ERR_JEB_NEW_SUPERIOR_NO_SUCH_OBJECT.get(
3120                              newSuperiorDN.toString());
3121              DN matchedDN = getMatchedDN(baseDN);
3122              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
3123                  msg, matchedDN, null);
3124            }
3125    
3126            if (newSuperiorID.compareTo(oldApexID) > 0)
3127            {
3128              // This move would break the above invariant so we must
3129              // renumber every entry that moves. This is even more
3130              // expensive since every entry has to be deleted from
3131              // and added back into the attribute indexes.
3132              newApexID = rootContainer.getNextEntryID();
3133    
3134              if(debugEnabled())
3135              {
3136                TRACER.debugInfo("Move of target entry requires renumbering" +
3137                    "all entries in the subtree. " +
3138                    "Old DN: %s " +
3139                    "New DN: %s " +
3140                    "Old entry ID: %d " +
3141                    "New entry ID: %d " +
3142                    "New Superior ID: %d" +
3143                    oldApexEntry.getDN(), newApexEntry.getDN(),
3144                    oldApexID.longValue(), newApexID.longValue(),
3145                    newSuperiorID.longValue());
3146              }
3147            }
3148          }
3149    
3150          // Move or rename the apex entry.
3151          renameApexEntry(txn, buffer, oldApexID, newApexID, oldApexEntry,
3152                newApexEntry);
3153    
3154          /*
3155           * We will iterate forwards through a range of the dn2id keys to
3156           * find subordinates of the target entry from the top of the tree
3157           * downwards.
3158           */
3159          byte[] suffix = StaticUtils.getBytes("," +
3160              oldApexDN.toNormalizedString());
3161    
3162          /*
3163           * Set the ending value to a value of equal length but slightly
3164           * greater than the suffix.
3165           */
3166          byte[] end = suffix.clone();
3167          end[0] = (byte) (end[0] + 1);
3168    
3169          // Set the starting value to the suffix.
3170          byte[] begin = suffix;
3171    
3172          DatabaseEntry data = new DatabaseEntry();
3173          DatabaseEntry key = new DatabaseEntry(begin);
3174          int subordinateEntriesMoved = 0;
3175    
3176          CursorConfig cursorConfig = new CursorConfig();
3177          cursorConfig.setReadCommitted(true);
3178          Cursor cursor = dn2id.openCursor(txn, cursorConfig);
3179          try
3180          {
3181            OperationStatus status;
3182    
3183            // Initialize the cursor very close to the starting value.
3184            status = cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
3185    
3186            // Step forward until the key is greater than the starting value.
3187            while (status == OperationStatus.SUCCESS &&
3188                dn2id.getComparator().compare(key.getData(), begin) <= 0)
3189            {
3190              status = cursor.getNext(key, data, LockMode.DEFAULT);
3191            }
3192    
3193            // Step forward until we pass the ending value.
3194            while (status == OperationStatus.SUCCESS)
3195            {
3196              int cmp = dn2id.getComparator().compare(key.getData(), end);
3197              if (cmp >= 0)
3198              {
3199                // We have gone past the ending value.
3200                break;
3201              }
3202    
3203              // We have found a subordinate entry.
3204    
3205              EntryID oldID = new EntryID(data);
3206              Entry oldEntry = id2entry.get(txn, oldID, LockMode.DEFAULT);
3207    
3208              // Construct the new DN of the entry.
3209              DN newDN = modDN(oldEntry.getDN(),
3210                               oldApexDN.getNumComponents(),
3211                               newApexEntry.getDN());
3212    
3213              // Assign a new entry ID if we are renumbering.
3214              EntryID newID = oldID;
3215              if (!newApexID.equals(oldApexID))
3216              {
3217                newID = rootContainer.getNextEntryID();
3218    
3219                if(debugEnabled())
3220                {
3221                  TRACER.debugInfo("Move of subordinate entry requires " +
3222                      "renumbering. " +
3223                      "Old DN: %s " +
3224                      "New DN: %s " +
3225                      "Old entry ID: %d " +
3226                      "New entry ID: %d",
3227                      oldEntry.getDN(), newDN, oldID.longValue(),
3228                      newID.longValue());
3229                }
3230              }
3231    
3232              // Move this entry.
3233              renameSubordinateEntry(txn, buffer, oldID, newID, oldEntry, newDN);
3234              subordinateEntriesMoved++;
3235    
3236              if(subordinateEntriesMoved >= subtreeDeleteBatchSize)
3237              {
3238                buffer.flush(txn);
3239                subordinateEntriesMoved = 0;
3240              }
3241    
3242              // Get the next DN.
3243              status = cursor.getNext(key, data, LockMode.DEFAULT);
3244            }
3245          }
3246          finally
3247          {
3248            cursor.close();
3249          }
3250    
3251          buffer.flush(txn);
3252        }
3253    
3254        /**
3255         * Begin a transaction for this operation.
3256         *
3257         * @return The transaction for the operation, or null if the operation
3258         *         will not use a transaction.
3259         * @throws DatabaseException If an error occurs in the JE database.
3260         */
3261        public Transaction beginOperationTransaction() throws DatabaseException
3262        {
3263          return beginTransaction();
3264        }
3265    
3266        /**
3267         * Update the database for the target entry of a Modify DN operation
3268         * not specifying a new superior.
3269         *
3270         * @param txn The database transaction to be used for the updates.
3271         * @param buffer The index buffer used to buffer up the index changes.
3272         * @param oldID The old ID of the target entry.
3273         * @param newID The new ID of the target entry.
3274         * @param oldEntry The original contents of the target entry.
3275         * @param newEntry The new contents of the target entry.
3276         * @throws DirectoryException If a Directory Server error occurs.
3277         * @throws DatabaseException If an error occurs in the JE database.
3278         * @throws JebException if an error occurs in the JE database.
3279         */
3280        private void renameApexEntry(Transaction txn, IndexBuffer buffer,
3281                                     EntryID oldID, EntryID newID,
3282                                     Entry oldEntry, Entry newEntry)
3283            throws DirectoryException, DatabaseException, JebException
3284        {
3285          DN oldDN = oldEntry.getDN();
3286          DN newDN = newEntry.getDN();
3287    
3288          // Remove the old DN from dn2id.
3289          dn2id.remove(txn, oldDN);
3290    
3291          // Put the new DN in dn2id.
3292          if (!dn2id.insert(txn, newDN, newID))
3293          {
3294            Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
3295            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
3296                message);
3297          }
3298    
3299          // Remove old ID from id2entry and put the new entry
3300          // (old entry with new DN) in id2entry.
3301          if (!newID.equals(oldID))
3302          {
3303            id2entry.remove(txn, oldID);
3304          }
3305          id2entry.put(txn, newID, newEntry);
3306    
3307          // Update any referral records.
3308          dn2uri.replaceEntry(txn, oldEntry, newEntry);
3309    
3310          // Remove the old ID from id2children and id2subtree of
3311          // the old apex parent entry.
3312          if(oldSuperiorDN != null && isApexEntryMoved)
3313          {
3314            EntryID parentID;
3315            byte[] parentIDKeyBytes;
3316            boolean isParent = true;
3317            for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn))
3318            {
3319              parentID = dn2id.get(txn, dn, LockMode.DEFAULT);
3320              parentIDKeyBytes =
3321                  JebFormat.entryIDToDatabase(parentID.longValue());
3322              if(isParent)
3323              {
3324                id2children.removeID(buffer, parentIDKeyBytes, oldID);
3325                isParent = false;
3326              }
3327              id2subtree.removeID(buffer, parentIDKeyBytes, oldID);
3328            }
3329          }
3330    
3331          if (!newID.equals(oldID) || modifyDNOperation == null)
3332          {
3333            // All the subordinates will be renumbered so we have to rebuild
3334            // id2c and id2s with the new ID.
3335            byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue());
3336            id2children.delete(buffer, oldIDKeyBytes);
3337            id2subtree.delete(buffer, oldIDKeyBytes);
3338    
3339            // Reindex the entry with the new ID.
3340            indexRemoveEntry(buffer, oldEntry, oldID);
3341            indexInsertEntry(buffer, newEntry, newID);
3342          }
3343          else
3344          {
3345            // Update the indexes if needed.
3346            indexModifications(buffer, oldEntry, newEntry, oldID,
3347                modifyDNOperation.getModifications());
3348          }
3349    
3350          // Add the new ID to id2children and id2subtree of new apex parent entry.
3351          if(newSuperiorDN != null && isApexEntryMoved)
3352          {
3353            EntryID parentID;
3354            byte[] parentIDKeyBytes;
3355            boolean isParent = true;
3356            for (DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn))
3357            {
3358              parentID = dn2id.get(txn, dn, LockMode.DEFAULT);
3359              parentIDKeyBytes =
3360                  JebFormat.entryIDToDatabase(parentID.longValue());
3361              if(isParent)
3362              {
3363                id2children.insertID(buffer, parentIDKeyBytes, newID);
3364                isParent = false;
3365              }
3366              id2subtree.insertID(buffer, parentIDKeyBytes, newID);
3367            }
3368          }
3369    
3370          // Remove the entry from the entry cache.
3371          EntryCache entryCache = DirectoryServer.getEntryCache();
3372          if (entryCache != null)
3373          {
3374            entryCache.removeEntry(oldDN);
3375          }
3376        }
3377    
3378        /**
3379         * Update the database for a subordinate entry of the target entry
3380         * of a Modify DN operation specifying a new superior.
3381         *
3382         * @param txn The database transaction to be used for the updates.
3383         * @param buffer The index buffer used to buffer up the index changes.
3384         * @param oldID The original ID of the subordinate entry.
3385         * @param newID The new ID of the subordinate entry, or the original ID if
3386         *              the ID has not changed.
3387         * @param oldEntry The original contents of the subordinate entry.
3388         * @param newDN The new DN of the subordinate entry.
3389         * @throws JebException If an error occurs in the JE backend.
3390         * @throws DirectoryException If a Directory Server error occurs.
3391         * @throws DatabaseException If an error occurs in the JE database.
3392         */
3393        private void renameSubordinateEntry(Transaction txn, IndexBuffer buffer,
3394                                            EntryID oldID, EntryID newID,
3395                                            Entry oldEntry, DN newDN)
3396            throws JebException, DirectoryException, DatabaseException
3397        {
3398          DN oldDN = oldEntry.getDN();
3399          Entry newEntry = oldEntry.duplicate(false);
3400          newEntry.setDN(newDN);
3401          List<Modification> modifications =
3402              Collections.unmodifiableList(new ArrayList<Modification>(0));
3403    
3404          // Create a new entry that is a copy of the old entry but with the new DN.
3405          // Also invoke any subordinate modify DN plugins on the entry.
3406          // FIXME -- At the present time, we don't support subordinate modify DN
3407          //          plugins that make changes to subordinate entries and therefore
3408          //          provide an unmodifiable list for the modifications element.
3409          // FIXME -- This will need to be updated appropriately if we decided that
3410          //          these plugins should be invoked for synchronization
3411          //          operations.
3412          if (! modifyDNOperation.isSynchronizationOperation())
3413          {
3414            PluginConfigManager pluginManager =
3415                DirectoryServer.getPluginConfigManager();
3416            PluginResult.SubordinateModifyDN pluginResult =
3417                pluginManager.invokeSubordinateModifyDNPlugins(
3418                    modifyDNOperation, oldEntry, newEntry, modifications);
3419    
3420            if (!pluginResult.continueProcessing())
3421            {
3422              Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get(
3423                  oldDN.toString(), newDN.toString());
3424              throw new DirectoryException(
3425                  DirectoryServer.getServerErrorResultCode(), message);
3426            }
3427    
3428            if (! modifications.isEmpty())
3429            {
3430              MessageBuilder invalidReason = new MessageBuilder();
3431              if (! newEntry.conformsToSchema(null, false, false, false,
3432                  invalidReason))
3433              {
3434                Message message =
3435                    ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get(
3436                        oldDN.toString(),
3437                        newDN.toString(),
3438                        invalidReason.toString());
3439                throw new DirectoryException(
3440                    DirectoryServer.getServerErrorResultCode(), message);
3441              }
3442            }
3443          }
3444    
3445          // Remove the old DN from dn2id.
3446          dn2id.remove(txn, oldDN);
3447    
3448          // Put the new DN in dn2id.
3449          if (!dn2id.insert(txn, newDN, newID))
3450          {
3451            Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
3452            throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
3453                message);
3454          }
3455    
3456          // Remove old ID from id2entry and put the new entry
3457          // (old entry with new DN) in id2entry.
3458          if (!newID.equals(oldID))
3459          {
3460            id2entry.remove(txn, oldID);
3461          }
3462          id2entry.put(txn, newID, newEntry);
3463    
3464          // Update any referral records.
3465          dn2uri.replaceEntry(txn, oldEntry, newEntry);
3466    
3467          if(isApexEntryMoved)
3468          {
3469            // Remove the old ID from id2subtree of old apex superior entries.
3470            for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn))
3471            {
3472              EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT);
3473              byte[] parentIDKeyBytes =
3474                  JebFormat.entryIDToDatabase(parentID.longValue());
3475              id2subtree.removeID(buffer, parentIDKeyBytes, oldID);
3476            }
3477          }
3478    
3479          if (!newID.equals(oldID))
3480          {
3481            // All the subordinates will be renumbered so we have to rebuild
3482            // id2c and id2s with the new ID.
3483            byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue());
3484            id2children.delete(buffer, oldIDKeyBytes);
3485            id2subtree.delete(buffer, oldIDKeyBytes);
3486    
3487            // Add new ID to the id2c and id2s of our new parent and
3488            // new ID to id2s up the tree.
3489            EntryID newParentID;
3490            byte[] parentIDKeyBytes;
3491            boolean isParent = true;
3492            for (DN superiorDN = newDN; superiorDN != null;
3493                 superiorDN = getParentWithinBase(superiorDN))
3494            {
3495              newParentID = dn2id.get(txn, superiorDN, LockMode.DEFAULT);
3496              parentIDKeyBytes =
3497                  JebFormat.entryIDToDatabase(newParentID.longValue());
3498              if(isParent)
3499              {
3500                id2children.insertID(buffer, parentIDKeyBytes, newID);
3501                isParent = false;
3502              }
3503              id2subtree.insertID(buffer, parentIDKeyBytes, newID);
3504            }
3505    
3506            // Reindex the entry with the new ID.
3507            indexRemoveEntry(buffer, oldEntry, oldID);
3508            indexInsertEntry(buffer, newEntry, newID);
3509          }
3510          else
3511          {
3512            // Update the indexes if needed.
3513            if(! modifications.isEmpty())
3514            {
3515              indexModifications(buffer, oldEntry, newEntry, oldID, modifications);
3516            }
3517    
3518            if(isApexEntryMoved)
3519            {
3520              // Add the new ID to the id2s of new apex superior entries.
3521              for(DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn))
3522              {
3523                EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT);
3524                byte[] parentIDKeyBytes =
3525                    JebFormat.entryIDToDatabase(parentID.longValue());
3526                id2subtree.insertID(buffer, parentIDKeyBytes, newID);
3527              }
3528            }
3529          }
3530    
3531          // Remove the entry from the entry cache.
3532          EntryCache entryCache = DirectoryServer.getEntryCache();
3533          if (entryCache != null)
3534          {
3535            entryCache.removeEntry(oldDN);
3536          }
3537        }
3538    
3539        /**
3540         * This method is called after the transaction has successfully
3541         * committed.
3542         */
3543        public void postCommitAction()
3544        {
3545          // No implementation needed.
3546        }
3547      }
3548    
3549      /**
3550       * Make a new DN for a subordinate entry of a renamed or moved entry.
3551       *
3552       * @param oldDN The current DN of the subordinate entry.
3553       * @param oldSuffixLen The current DN length of the renamed or moved entry.
3554       * @param newSuffixDN The new DN of the renamed or moved entry.
3555       * @return The new DN of the subordinate entry.
3556       */
3557      public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN)
3558      {
3559        int oldDNNumComponents    = oldDN.getNumComponents();
3560        int oldDNKeepComponents   = oldDNNumComponents - oldSuffixLen;
3561        int newSuffixDNComponents = newSuffixDN.getNumComponents();
3562    
3563        RDN[] newDNComponents = new RDN[oldDNKeepComponents+newSuffixDNComponents];
3564        for (int i=0; i < oldDNKeepComponents; i++)
3565        {
3566          newDNComponents[i] = oldDN.getRDN(i);
3567        }
3568    
3569        for (int i=oldDNKeepComponents, j=0; j < newSuffixDNComponents; i++,j++)
3570        {
3571          newDNComponents[i] = newSuffixDN.getRDN(j);
3572        }
3573    
3574        return new DN(newDNComponents);
3575      }
3576    
3577      /**
3578       * A lexicographic byte array comparator that compares in
3579       * reverse byte order. This is used for the dn2id database.
3580       * If we want to find all the entries in a subtree dc=com we know that
3581       * all subordinate entries must have ,dc=com as a common suffix. In reversing
3582       * the order of comparison we turn the subtree base into a common prefix
3583       * and are able to iterate through the keys having that prefix.
3584       */
3585      static public class KeyReverseComparator implements Comparator<byte[]>
3586      {
3587        /**
3588         * Compares its two arguments for order.  Returns a negative integer,
3589         * zero, or a positive integer as the first argument is less than, equal
3590         * to, or greater than the second.
3591         *
3592         * @param a the first object to be compared.
3593         * @param b the second object to be compared.
3594         * @return a negative integer, zero, or a positive integer as the
3595         *         first argument is less than, equal to, or greater than the
3596         *         second.
3597         */
3598        public int compare(byte[] a, byte[] b)
3599        {
3600          for (int ai = a.length - 1, bi = b.length - 1;
3601               ai >= 0 && bi >= 0; ai--, bi--)
3602          {
3603            if (a[ai] > b[bi])
3604            {
3605              return 1;
3606            }
3607            else if (a[ai] < b[bi])
3608            {
3609              return -1;
3610            }
3611          }
3612          if (a.length == b.length)
3613          {
3614            return 0;
3615          }
3616          if (a.length > b.length)
3617          {
3618            return 1;
3619          }
3620          else
3621          {
3622            return -1;
3623          }
3624        }
3625      }
3626    
3627      /**
3628       * Insert a new entry into the attribute indexes.
3629       *
3630       * @param txn The database transaction to be used for the updates.
3631       * @param entry The entry to be inserted into the indexes.
3632       * @param entryID The ID of the entry to be inserted into the indexes.
3633       * @throws DatabaseException If an error occurs in the JE database.
3634       * @throws DirectoryException If a Directory Server error occurs.
3635       * @throws JebException If an error occurs in the JE backend.
3636       */
3637      private void indexInsertEntry(Transaction txn, Entry entry, EntryID entryID)
3638          throws DatabaseException, DirectoryException, JebException
3639      {
3640        for (AttributeIndex index : attrIndexMap.values())
3641        {
3642          index.addEntry(txn, entryID, entry);
3643        }
3644    
3645        for (VLVIndex vlvIndex : vlvIndexMap.values())
3646        {
3647          vlvIndex.addEntry(txn, entryID, entry);
3648        }
3649      }
3650    
3651      /**
3652       * Insert a new entry into the attribute indexes.
3653       *
3654       * @param buffer The index buffer used to buffer up the index changes.
3655       * @param entry The entry to be inserted into the indexes.
3656       * @param entryID The ID of the entry to be inserted into the indexes.
3657       * @throws DatabaseException If an error occurs in the JE database.
3658       * @throws DirectoryException If a Directory Server error occurs.
3659       * @throws JebException If an error occurs in the JE backend.
3660       */
3661      private void indexInsertEntry(IndexBuffer buffer, Entry entry,
3662                                    EntryID entryID)
3663          throws DatabaseException, DirectoryException, JebException
3664      {
3665        for (AttributeIndex index : attrIndexMap.values())
3666        {
3667          index.addEntry(buffer, entryID, entry);
3668        }
3669    
3670        for (VLVIndex vlvIndex : vlvIndexMap.values())
3671        {
3672          vlvIndex.addEntry(buffer, entryID, entry);
3673        }
3674      }
3675    
3676      /**
3677       * Remove an entry from the attribute indexes.
3678       *
3679       * @param txn The database transaction to be used for the updates.
3680       * @param entry The entry to be removed from the indexes.
3681       * @param entryID The ID of the entry to be removed from the indexes.
3682       * @throws DatabaseException If an error occurs in the JE database.
3683       * @throws DirectoryException If a Directory Server error occurs.
3684       * @throws JebException If an error occurs in the JE backend.
3685       */
3686      private void indexRemoveEntry(Transaction txn, Entry entry, EntryID entryID)
3687          throws DatabaseException, DirectoryException, JebException
3688      {
3689        for (AttributeIndex index : attrIndexMap.values())
3690        {
3691          index.removeEntry(txn, entryID, entry);
3692        }
3693    
3694        for (VLVIndex vlvIndex : vlvIndexMap.values())
3695        {
3696          vlvIndex.removeEntry(txn, entryID, entry);
3697        }
3698      }
3699    
3700      /**
3701       * Remove an entry from the attribute indexes.
3702       *
3703       * @param buffer The index buffer used to buffer up the index changes.
3704       * @param entry The entry to be removed from the indexes.
3705       * @param entryID The ID of the entry to be removed from the indexes.
3706       * @throws DatabaseException If an error occurs in the JE database.
3707       * @throws DirectoryException If a Directory Server error occurs.
3708       * @throws JebException If an error occurs in the JE backend.
3709       */
3710      private void indexRemoveEntry(IndexBuffer buffer, Entry entry,
3711                                    EntryID entryID)
3712          throws DatabaseException, DirectoryException, JebException
3713      {
3714        for (AttributeIndex index : attrIndexMap.values())
3715        {
3716          index.removeEntry(buffer, entryID, entry);
3717        }
3718    
3719        for (VLVIndex vlvIndex : vlvIndexMap.values())
3720        {
3721          vlvIndex.removeEntry(buffer, entryID, entry);
3722        }
3723      }
3724    
3725      /**
3726       * Update the attribute indexes to reflect the changes to the
3727       * attributes of an entry resulting from a sequence of modifications.
3728       *
3729       * @param txn The database transaction to be used for the updates.
3730       * @param oldEntry The contents of the entry before the change.
3731       * @param newEntry The contents of the entry after the change.
3732       * @param entryID The ID of the entry that was changed.
3733       * @param mods The sequence of modifications made to the entry.
3734       * @throws DatabaseException If an error occurs in the JE database.
3735       * @throws DirectoryException If a Directory Server error occurs.
3736       * @throws JebException If an error occurs in the JE backend.
3737       */
3738      private void indexModifications(Transaction txn, Entry oldEntry,
3739                                      Entry newEntry,
3740                                      EntryID entryID, List<Modification> mods)
3741          throws DatabaseException, DirectoryException, JebException
3742      {
3743        // Process in index configuration order.
3744        for (AttributeIndex index : attrIndexMap.values())
3745        {
3746          // Check whether any modifications apply to this indexed attribute.
3747          boolean attributeModified = false;
3748          AttributeType indexAttributeType = index.getAttributeType();
3749          Iterable<AttributeType> subTypes =
3750              DirectoryServer.getSchema().getSubTypes(indexAttributeType);
3751    
3752          for (Modification mod : mods)
3753          {
3754            Attribute modAttr = mod.getAttribute();
3755            AttributeType modAttrType = modAttr.getAttributeType();
3756            if (modAttrType.equals(indexAttributeType))
3757            {
3758              attributeModified = true;
3759              break;
3760            }
3761            for(AttributeType subType : subTypes)
3762            {
3763              if(modAttrType.equals(subType))
3764              {
3765                attributeModified = true;
3766                break;
3767              }
3768            }
3769          }
3770          if (attributeModified)
3771          {
3772            index.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
3773          }
3774        }
3775    
3776        for(VLVIndex vlvIndex : vlvIndexMap.values())
3777        {
3778          vlvIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
3779        }
3780      }
3781    
3782      /**
3783       * Update the attribute indexes to reflect the changes to the
3784       * attributes of an entry resulting from a sequence of modifications.
3785       *
3786       * @param buffer The index buffer used to buffer up the index changes.
3787       * @param oldEntry The contents of the entry before the change.
3788       * @param newEntry The contents of the entry after the change.
3789       * @param entryID The ID of the entry that was changed.
3790       * @param mods The sequence of modifications made to the entry.
3791       * @throws DatabaseException If an error occurs in the JE database.
3792       * @throws DirectoryException If a Directory Server error occurs.
3793       * @throws JebException If an error occurs in the JE backend.
3794       */
3795      private void indexModifications(IndexBuffer buffer, Entry oldEntry,
3796                                      Entry newEntry,
3797                                      EntryID entryID, List<Modification> mods)
3798          throws DatabaseException, DirectoryException, JebException
3799      {
3800        // Process in index configuration order.
3801        for (AttributeIndex index : attrIndexMap.values())
3802        {
3803          // Check whether any modifications apply to this indexed attribute.
3804          boolean attributeModified = false;
3805          AttributeType indexAttributeType = index.getAttributeType();
3806          Iterable<AttributeType> subTypes =
3807              DirectoryServer.getSchema().getSubTypes(indexAttributeType);
3808    
3809          for (Modification mod : mods)
3810          {
3811            Attribute modAttr = mod.getAttribute();
3812            AttributeType modAttrType = modAttr.getAttributeType();
3813            if (modAttrType.equals(indexAttributeType))
3814            {
3815              attributeModified = true;
3816              break;
3817            }
3818            for(AttributeType subType : subTypes)
3819            {
3820              if(modAttrType.equals(subType))
3821              {
3822                attributeModified = true;
3823                break;
3824              }
3825            }
3826          }
3827          if (attributeModified)
3828          {
3829            index.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
3830          }
3831        }
3832    
3833        for(VLVIndex vlvIndex : vlvIndexMap.values())
3834        {
3835          vlvIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
3836        }
3837      }
3838    
3839      /**
3840       * Get a count of the number of entries stored in this entry entryContainer.
3841       *
3842       * @return The number of entries stored in this entry entryContainer.
3843       * @throws DatabaseException If an error occurs in the JE database.
3844       */
3845      public long getEntryCount() throws DatabaseException
3846      {
3847        EntryID entryID = dn2id.get(null, baseDN, LockMode.DEFAULT);
3848        if (entryID != null)
3849        {
3850          DatabaseEntry key =
3851              new DatabaseEntry(JebFormat.entryIDToDatabase(entryID.longValue()));
3852          EntryIDSet entryIDSet;
3853          entryIDSet = id2subtree.readKey(key, null, LockMode.DEFAULT);
3854    
3855          long count = entryIDSet.size();
3856          if(count != Long.MAX_VALUE)
3857          {
3858            // Add the base entry itself
3859            return ++count;
3860          }
3861          else
3862          {
3863            // The count is not maintained. Fall back to the slow method
3864            return id2entry.getRecordCount();
3865          }
3866        }
3867        else
3868        {
3869          // Base entry doesn't not exist so this entry container
3870          // must not have any entries
3871          return 0;
3872        }
3873      }
3874    
3875      /**
3876       * Get the number of values for which the entry limit has been exceeded
3877       * since the entry entryContainer was opened.
3878       * @return The number of values for which the entry limit has been exceeded.
3879       */
3880      public int getEntryLimitExceededCount()
3881      {
3882        int count = 0;
3883        count += id2children.getEntryLimitExceededCount();
3884        count += id2subtree.getEntryLimitExceededCount();
3885        for (AttributeIndex index : attrIndexMap.values())
3886        {
3887          count += index.getEntryLimitExceededCount();
3888        }
3889        return count;
3890      }
3891    
3892      /**
3893       * Get a list of the databases opened by this entryContainer.
3894       * @param dbList A list of database containers.
3895       */
3896      public void listDatabases(List<DatabaseContainer> dbList)
3897      {
3898        dbList.add(dn2id);
3899        dbList.add(id2entry);
3900        dbList.add(dn2uri);
3901        dbList.add(id2children);
3902        dbList.add(id2subtree);
3903        dbList.add(state);
3904    
3905        for(AttributeIndex index : attrIndexMap.values())
3906        {
3907          index.listDatabases(dbList);
3908        }
3909    
3910        for (VLVIndex vlvIndex : vlvIndexMap.values())
3911        {
3912          dbList.add(vlvIndex);
3913        }
3914      }
3915    
3916      /**
3917       * Determine whether the provided operation has the ManageDsaIT request
3918       * control.
3919       * @param operation The operation for which the determination is to be made.
3920       * @return true if the operation has the ManageDsaIT request control, or false
3921       * if not.
3922       */
3923      public static boolean isManageDsaITOperation(Operation operation)
3924      {
3925        if(operation != null)
3926        {
3927          List<Control> controls = operation.getRequestControls();
3928          if (controls != null)
3929          {
3930            for (Control control : controls)
3931            {
3932              if (control.getOID().equals(ServerConstants.OID_MANAGE_DSAIT_CONTROL))
3933              {
3934                return true;
3935              }
3936            }
3937          }
3938        }
3939        return false;
3940      }
3941    
3942      /**
3943       * Begin a leaf transaction using the default configuration.
3944       * Provides assertion debug logging.
3945       * @return A JE transaction handle.
3946       * @throws DatabaseException If an error occurs while attempting to begin
3947       * a new transaction.
3948       */
3949      public Transaction beginTransaction()
3950          throws DatabaseException
3951      {
3952        Transaction parentTxn = null;
3953        TransactionConfig txnConfig = null;
3954        Transaction txn = env.beginTransaction(parentTxn, txnConfig);
3955        if (debugEnabled())
3956        {
3957          TRACER.debugVerbose("beginTransaction", "begin txnid=" + txn.getId());
3958        }
3959        return txn;
3960      }
3961    
3962      /**
3963       * Commit a transaction.
3964       * Provides assertion debug logging.
3965       * @param txn The JE transaction handle.
3966       * @throws DatabaseException If an error occurs while attempting to commit
3967       * the transaction.
3968       */
3969      public static void transactionCommit(Transaction txn)
3970          throws DatabaseException
3971      {
3972        if (txn != null)
3973        {
3974          txn.commit();
3975          if (debugEnabled())
3976          {
3977            TRACER.debugVerbose("commit txnid=%d", txn.getId());
3978          }
3979        }
3980      }
3981    
3982      /**
3983       * Abort a transaction.
3984       * Provides assertion debug logging.
3985       * @param txn The JE transaction handle.
3986       * @throws DatabaseException If an error occurs while attempting to abort the
3987       * transaction.
3988       */
3989      public static void transactionAbort(Transaction txn)
3990          throws DatabaseException
3991      {
3992        if (txn != null)
3993        {
3994          txn.abort();
3995          if (debugEnabled())
3996          {
3997            TRACER.debugVerbose("abort txnid=%d", txn.getId());
3998          }
3999        }
4000      }
4001    
4002      /**
4003       * Delete this entry container from disk. The entry container should be
4004       * closed before calling this method.
4005       *
4006       * @throws DatabaseException If an error occurs while removing the entry
4007       *                           container.
4008       */
4009      public void delete() throws DatabaseException
4010      {
4011        List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
4012        listDatabases(databases);
4013    
4014        for(DatabaseContainer db : databases)
4015        {
4016          db.close();
4017        }
4018    
4019        if(env.getConfig().getTransactional())
4020        {
4021          Transaction txn = beginTransaction();
4022    
4023          try
4024          {
4025            for(DatabaseContainer db : databases)
4026            {
4027              env.removeDatabase(txn, db.getName());
4028            }
4029    
4030            transactionCommit(txn);
4031          }
4032          catch(DatabaseException de)
4033          {
4034            transactionAbort(txn);
4035            throw de;
4036          }
4037        }
4038        else
4039        {
4040          for(DatabaseContainer db : databases)
4041          {
4042            env.removeDatabase(null, db.getName());
4043          }
4044        }
4045      }
4046    
4047      /**
4048       * Remove a database from disk.
4049       *
4050       * @param database The database container to remove.
4051       * @throws DatabaseException If an error occurs while attempting to delete the
4052       * database.
4053       */
4054      public void deleteDatabase(DatabaseContainer database)
4055          throws DatabaseException
4056      {
4057        if(database == state)
4058        {
4059          // The state database can not be removed individually.
4060          return;
4061        }
4062    
4063        database.close();
4064        if(env.getConfig().getTransactional())
4065        {
4066          Transaction txn = beginTransaction();
4067          try
4068          {
4069            env.removeDatabase(txn, database.getName());
4070            if(database instanceof Index)
4071            {
4072              state.removeIndexTrustState(txn, (Index)database);
4073            }
4074            transactionCommit(txn);
4075          }
4076          catch(DatabaseException de)
4077          {
4078            transactionAbort(txn);
4079            throw de;
4080          }
4081        }
4082        else
4083        {
4084          env.removeDatabase(null, database.getName());
4085          if(database instanceof Index)
4086          {
4087            state.removeIndexTrustState(null, (Index)database);
4088          }
4089        }
4090      }
4091    
4092      /**
4093       * Removes a attribute index from disk.
4094       *
4095       * @param index The attribute index to remove.
4096       * @throws DatabaseException If an JE database error occurs while attempting
4097       * to delete the index.
4098       */
4099      public void deleteAttributeIndex(AttributeIndex index)
4100          throws DatabaseException
4101      {
4102        index.close();
4103        if(env.getConfig().getTransactional())
4104        {
4105          Transaction txn = beginTransaction();
4106          try
4107          {
4108            if(index.equalityIndex != null)
4109            {
4110              env.removeDatabase(txn, index.equalityIndex.getName());
4111              state.removeIndexTrustState(txn, index.equalityIndex);
4112            }
4113            if(index.presenceIndex != null)
4114            {
4115              env.removeDatabase(txn, index.presenceIndex.getName());
4116              state.removeIndexTrustState(txn, index.presenceIndex);
4117            }
4118            if(index.substringIndex != null)
4119            {
4120              env.removeDatabase(txn, index.substringIndex.getName());
4121              state.removeIndexTrustState(txn, index.substringIndex);
4122            }
4123            if(index.orderingIndex != null)
4124            {
4125              env.removeDatabase(txn, index.orderingIndex.getName());
4126              state.removeIndexTrustState(txn, index.orderingIndex);
4127            }
4128            if(index.approximateIndex != null)
4129            {
4130              env.removeDatabase(txn, index.approximateIndex.getName());
4131              state.removeIndexTrustState(txn, index.approximateIndex);
4132            }
4133            transactionCommit(txn);
4134          }
4135          catch(DatabaseException de)
4136          {
4137            transactionAbort(txn);
4138            throw de;
4139          }
4140        }
4141        else
4142        {
4143          if(index.equalityIndex != null)
4144          {
4145            env.removeDatabase(null, index.equalityIndex.getName());
4146            state.removeIndexTrustState(null, index.equalityIndex);
4147          }
4148          if(index.presenceIndex != null)
4149          {
4150            env.removeDatabase(null, index.presenceIndex.getName());
4151            state.removeIndexTrustState(null, index.presenceIndex);
4152          }
4153          if(index.substringIndex != null)
4154          {
4155            env.removeDatabase(null, index.substringIndex.getName());
4156            state.removeIndexTrustState(null, index.substringIndex);
4157          }
4158          if(index.orderingIndex != null)
4159          {
4160            env.removeDatabase(null, index.orderingIndex.getName());
4161            state.removeIndexTrustState(null, index.orderingIndex);
4162          }
4163          if(index.approximateIndex != null)
4164          {
4165            env.removeDatabase(null, index.approximateIndex.getName());
4166            state.removeIndexTrustState(null, index.approximateIndex);
4167          }
4168        }
4169      }
4170    
4171      /**
4172       * This method constructs a container name from a base DN. Only alphanumeric
4173       * characters are preserved, all other characters are replaced with an
4174       * underscore.
4175       *
4176       * @return The container name for the base DN.
4177       */
4178      public String getDatabasePrefix()
4179      {
4180        return databasePrefix;
4181      }
4182    
4183      /**
4184       * Sets a new database prefix for this entry container and rename all
4185       * existing databases in use by this entry container.
4186       *
4187       * @param newDatabasePrefix The new database prefix to use.
4188       * @throws DatabaseException If an error occurs in the JE database.
4189       * @throws JebException If an error occurs in the JE backend.
4190       */
4191      public void setDatabasePrefix(String newDatabasePrefix)
4192          throws DatabaseException, JebException
4193    
4194      {
4195        List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
4196        listDatabases(databases);
4197    
4198        StringBuilder builder = new StringBuilder(newDatabasePrefix.length());
4199        for (int i = 0; i < newDatabasePrefix.length(); i++)
4200        {
4201          char ch = newDatabasePrefix.charAt(i);
4202          if (Character.isLetterOrDigit(ch))
4203          {
4204            builder.append(ch);
4205          }
4206          else
4207          {
4208            builder.append('_');
4209          }
4210        }
4211        newDatabasePrefix = builder.toString();
4212    
4213        // close the containers.
4214        for(DatabaseContainer db : databases)
4215        {
4216          db.close();
4217        }
4218    
4219        try
4220        {
4221          if(env.getConfig().getTransactional())
4222          {
4223            //Rename under transaction
4224            Transaction txn = beginTransaction();
4225            try
4226            {
4227              for(DatabaseContainer db : databases)
4228              {
4229                String oldName = db.getName();
4230                String newName = oldName.replace(databasePrefix, newDatabasePrefix);
4231                env.renameDatabase(txn, oldName, newName);
4232              }
4233    
4234              transactionCommit(txn);
4235    
4236              for(DatabaseContainer db : databases)
4237              {
4238                String oldName = db.getName();
4239                String newName = oldName.replace(databasePrefix, newDatabasePrefix);
4240                db.setName(newName);
4241              }
4242    
4243              // Update the prefix.
4244              this.databasePrefix = newDatabasePrefix;
4245            }
4246            catch(Exception e)
4247            {
4248              transactionAbort(txn);
4249    
4250              Message message = ERR_JEB_UNCHECKED_EXCEPTION.get();
4251              throw new JebException(message, e);
4252            }
4253          }
4254          else
4255          {
4256            for(DatabaseContainer db : databases)
4257            {
4258              String oldName = db.getName();
4259              String newName = oldName.replace(databasePrefix, newDatabasePrefix);
4260              env.renameDatabase(null, oldName, newName);
4261              db.setName(newName);
4262            }
4263    
4264            // Update the prefix.
4265            this.databasePrefix = newDatabasePrefix;
4266          }
4267        }
4268        finally
4269        {
4270          // Open the containers backup.
4271          for(DatabaseContainer db : databases)
4272          {
4273            db.open();
4274          }
4275        }
4276      }
4277    
4278    
4279      /**
4280       * Get the baseDN this entry container is responsible for.
4281       *
4282       * @return The Base DN for this entry container.
4283       */
4284      public DN getBaseDN()
4285      {
4286        return baseDN;
4287      }
4288    
4289      /**
4290       * Get the parent of a DN in the scope of the base DN.
4291       *
4292       * @param dn A DN which is in the scope of the base DN.
4293       * @return The parent DN, or null if the given DN is the base DN.
4294       */
4295      public DN getParentWithinBase(DN dn)
4296      {
4297        if (dn.equals(baseDN))
4298        {
4299          return null;
4300        }
4301        return dn.getParent();
4302      }
4303    
4304      /**
4305       * {@inheritDoc}
4306       */
4307      public synchronized boolean isConfigurationChangeAcceptable(
4308          LocalDBBackendCfg cfg, List<Message> unacceptableReasons)
4309      {
4310        // This is always true because only all config attributes used
4311        // by the entry container should be validated by the admin framework.
4312        return true;
4313      }
4314    
4315      /**
4316       * {@inheritDoc}
4317       */
4318      public synchronized ConfigChangeResult applyConfigurationChange(
4319          LocalDBBackendCfg cfg)
4320      {
4321        boolean adminActionRequired = false;
4322        ArrayList<Message> messages = new ArrayList<Message>();
4323    
4324        if(config.getIndexEntryLimit() != cfg.getIndexEntryLimit())
4325        {
4326          if(id2children.setIndexEntryLimit(cfg.getIndexEntryLimit()))
4327          {
4328            adminActionRequired = true;
4329            Message message =
4330                    NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
4331                            id2children.getName());
4332            messages.add(message);
4333          }
4334    
4335          if(id2subtree.setIndexEntryLimit(cfg.getIndexEntryLimit()))
4336          {
4337            adminActionRequired = true;
4338            Message message =
4339                    NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
4340                            id2subtree.getName());
4341            messages.add(message);
4342          }
4343        }
4344    
4345        DataConfig entryDataConfig =
4346            new DataConfig(cfg.isEntriesCompressed(),
4347                           cfg.isCompactEncoding(),
4348                           rootContainer.getCompressedSchema());
4349        id2entry.setDataConfig(entryDataConfig);
4350    
4351        this.config = cfg;
4352        this.deadlockRetryLimit = config.getDeadlockRetryLimit();
4353        this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit();
4354        this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize();
4355        return new ConfigChangeResult(ResultCode.SUCCESS,
4356                                      adminActionRequired, messages);
4357      }
4358    
4359      /**
4360       * Get the environment config of the JE environment used in this entry
4361       * container.
4362       *
4363       * @return The environment config of the JE environment.
4364       * @throws DatabaseException If an error occurs while retriving the
4365       *                           configuration object.
4366       */
4367      public EnvironmentConfig getEnvironmentConfig()
4368          throws DatabaseException
4369      {
4370        return env.getConfig();
4371      }
4372    
4373      /**
4374       * Clear the contents of this entry container.
4375       *
4376       * @return The number of records deleted.
4377       * @throws DatabaseException If an error occurs while removing the entry
4378       *                           container.
4379       */
4380      public long clear() throws DatabaseException
4381      {
4382        List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
4383        listDatabases(databases);
4384        long count = 0;
4385    
4386        for(DatabaseContainer db : databases)
4387        {
4388          db.close();
4389        }
4390        try
4391        {
4392          if(env.getConfig().getTransactional())
4393          {
4394            Transaction txn = beginTransaction();
4395    
4396            try
4397            {
4398              for(DatabaseContainer db : databases)
4399              {
4400                count += env.truncateDatabase(txn, db.getName(), true);
4401              }
4402    
4403              transactionCommit(txn);
4404            }
4405            catch(DatabaseException de)
4406            {
4407              transactionAbort(txn);
4408              throw de;
4409            }
4410          }
4411          else
4412          {
4413            for(DatabaseContainer db : databases)
4414            {
4415              count += env.truncateDatabase(null, db.getName(), true);
4416            }
4417          }
4418        }
4419        finally
4420        {
4421          for(DatabaseContainer db : databases)
4422          {
4423            db.open();
4424          }
4425    
4426          Transaction txn = null;
4427          try
4428          {
4429            if(env.getConfig().getTransactional()) {
4430              txn = beginTransaction();
4431            }
4432            for(DatabaseContainer db : databases)
4433            {
4434              if (db instanceof Index)
4435              {
4436                Index index = (Index)db;
4437                index.setTrusted(txn, true);
4438              }
4439            }
4440            if(env.getConfig().getTransactional()) {
4441              transactionCommit(txn);
4442            }
4443          }
4444          catch(Exception de)
4445          {
4446            if (debugEnabled())
4447            {
4448              TRACER.debugCaught(DebugLogLevel.ERROR, de);
4449            }
4450    
4451            // This is mainly used during the unit tests, so it's not essential.
4452            try
4453            {
4454              if (txn != null)
4455              {
4456                transactionAbort(txn);
4457              }
4458            }
4459            catch (Exception e)
4460            {
4461              if (debugEnabled())
4462              {
4463                TRACER.debugCaught(DebugLogLevel.ERROR, de);
4464              }
4465            }
4466          }
4467        }
4468    
4469        return count;
4470      }
4471    
4472      /**
4473       * Clear the contents for a database from disk.
4474       *
4475       * @param database The database to clear.
4476       * @return The number of records deleted.
4477       * @throws DatabaseException if a JE database error occurs.
4478       */
4479      public long clearDatabase(DatabaseContainer database)
4480          throws DatabaseException
4481      {
4482        long count = 0;
4483        database.close();
4484        try
4485        {
4486          if(env.getConfig().getTransactional())
4487          {
4488            Transaction txn = beginTransaction();
4489            try
4490            {
4491              count = env.truncateDatabase(txn, database.getName(), true);
4492              transactionCommit(txn);
4493            }
4494            catch(DatabaseException de)
4495            {
4496              transactionAbort(txn);
4497              throw de;
4498            }
4499          }
4500          else
4501          {
4502            count = env.truncateDatabase(null, database.getName(), true);
4503          }
4504        }
4505        finally
4506        {
4507          database.open();
4508        }
4509        if(debugEnabled())
4510        {
4511          TRACER.debugVerbose("Cleared %d existing records from the " +
4512              "database %s", count, database.getName());
4513        }
4514        return count;
4515      }
4516    
4517      /**
4518       * Clear the contents for a attribute index from disk.
4519       *
4520       * @param index The attribute index to clear.
4521       * @return The number of records deleted.
4522       * @throws DatabaseException if a JE database error occurs.
4523       */
4524      public long clearAttributeIndex(AttributeIndex index)
4525          throws DatabaseException
4526      {
4527        long count = 0;
4528    
4529        index.close();
4530        try
4531        {
4532          if(env.getConfig().getTransactional())
4533          {
4534            Transaction txn = beginTransaction();
4535            try
4536            {
4537              if(index.equalityIndex != null)
4538              {
4539                count += env.truncateDatabase(txn, index.equalityIndex.getName(),
4540                                              true);
4541              }
4542              if(index.presenceIndex != null)
4543              {
4544                count += env.truncateDatabase(txn, index.presenceIndex.getName(),
4545                                              true);
4546              }
4547              if(index.substringIndex != null)
4548              {
4549                count += env.truncateDatabase(txn, index.substringIndex.getName(),
4550                                              true);
4551              }
4552              if(index.orderingIndex != null)
4553              {
4554                count += env.truncateDatabase(txn, index.orderingIndex.getName(),
4555                                              true);
4556              }
4557              if(index.approximateIndex != null)
4558              {
4559                count += env.truncateDatabase(txn, index.approximateIndex.getName(),
4560                                              true);
4561              }
4562              transactionCommit(txn);
4563            }
4564            catch(DatabaseException de)
4565            {
4566              transactionAbort(txn);
4567              throw de;
4568            }
4569          }
4570          else
4571          {
4572            if(index.equalityIndex != null)
4573            {
4574              count += env.truncateDatabase(null, index.equalityIndex.getName(),
4575                                            true);
4576            }
4577            if(index.presenceIndex != null)
4578            {
4579              count += env.truncateDatabase(null, index.presenceIndex.getName(),
4580                                            true);
4581            }
4582            if(index.substringIndex != null)
4583            {
4584              count += env.truncateDatabase(null, index.substringIndex.getName(),
4585                                            true);
4586            }
4587            if(index.orderingIndex != null)
4588            {
4589              count += env.truncateDatabase(null, index.orderingIndex.getName(),
4590                                            true);
4591            }
4592            if(index.approximateIndex != null)
4593            {
4594              count += env.truncateDatabase(null, index.approximateIndex.getName(),
4595                                            true);
4596            }
4597          }
4598        }
4599        finally
4600        {
4601          index.open();
4602        }
4603        if(debugEnabled())
4604        {
4605          TRACER.debugVerbose("Cleared %d existing records from the " +
4606              "index %s", count, index.getAttributeType().getNameOrOID());
4607        }
4608        return count;
4609      }
4610    
4611    
4612      /**
4613       * Finds an existing entry whose DN is the closest ancestor of a given baseDN.
4614       *
4615       * @param baseDN  the DN for which we are searching a matched DN
4616       * @return the DN of the closest ancestor of the baseDN
4617       * @throws DirectoryException If an error prevented the check of an
4618       * existing entry from being performed
4619       */
4620      private DN getMatchedDN(DN baseDN)
4621        throws DirectoryException
4622      {
4623        DN matchedDN = null;
4624        DN parentDN  = baseDN.getParentDNInSuffix();
4625        while ((parentDN != null) && parentDN.isDescendantOf(getBaseDN()))
4626        {
4627          if (entryExists(parentDN))
4628          {
4629            matchedDN = parentDN;
4630            break;
4631          }
4632          parentDN = parentDN.getParentDNInSuffix();
4633        }
4634        return matchedDN;
4635      }
4636    
4637      /**
4638       * Get the exclusive lock.
4639       */
4640      public void lock() {
4641        exclusiveLock.lock();
4642      }
4643    
4644      /**
4645       * Unlock the exclusive lock.
4646       */
4647      public void unlock() {
4648        exclusiveLock.unlock();
4649      }
4650    
4651      /**
4652       * Get the subtree delete batch size.
4653       *
4654       * @return The subtree delete batch size.
4655       */
4656      public int getSubtreeDeleteBatchSize()
4657      {
4658        return subtreeDeleteBatchSize;
4659      }
4660    
4661    }