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 }