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 }