001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.server.core.partition.impl.btree; 021 022 023import java.io.IOException; 024import java.io.OutputStream; 025import java.net.URI; 026import java.util.Arrays; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032import java.util.Set; 033import java.util.concurrent.Semaphore; 034import java.util.concurrent.atomic.AtomicBoolean; 035import java.util.concurrent.locks.ReadWriteLock; 036import java.util.concurrent.locks.ReentrantReadWriteLock; 037 038import net.sf.ehcache.Cache; 039import net.sf.ehcache.Element; 040import net.sf.ehcache.config.CacheConfiguration; 041import net.sf.ehcache.store.LruPolicy; 042 043import org.apache.directory.api.ldap.model.constants.SchemaConstants; 044import org.apache.directory.api.ldap.model.cursor.Cursor; 045import org.apache.directory.api.ldap.model.cursor.CursorException; 046import org.apache.directory.api.ldap.model.entry.Attribute; 047import org.apache.directory.api.ldap.model.entry.Entry; 048import org.apache.directory.api.ldap.model.entry.Modification; 049import org.apache.directory.api.ldap.model.entry.Value; 050import org.apache.directory.api.ldap.model.exception.LdapAliasDereferencingException; 051import org.apache.directory.api.ldap.model.exception.LdapAliasException; 052import org.apache.directory.api.ldap.model.exception.LdapContextNotEmptyException; 053import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException; 054import org.apache.directory.api.ldap.model.exception.LdapException; 055import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 056import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException; 057import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException; 058import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; 059import org.apache.directory.api.ldap.model.exception.LdapOtherException; 060import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; 061import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; 062import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 063import org.apache.directory.api.ldap.model.name.Ava; 064import org.apache.directory.api.ldap.model.name.Dn; 065import org.apache.directory.api.ldap.model.name.Rdn; 066import org.apache.directory.api.ldap.model.schema.AttributeType; 067import org.apache.directory.api.ldap.model.schema.MatchingRule; 068import org.apache.directory.api.ldap.model.schema.Normalizer; 069import org.apache.directory.api.ldap.model.schema.SchemaManager; 070import org.apache.directory.api.util.Strings; 071import org.apache.directory.api.util.exception.MultiException; 072import org.apache.directory.server.constants.ApacheSchemaConstants; 073import org.apache.directory.server.core.api.DnFactory; 074import org.apache.directory.server.core.api.entry.ClonedServerEntry; 075import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 076import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl; 077import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 078import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 079import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; 080import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 081import org.apache.directory.server.core.api.interceptor.context.ModDnAva; 082import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 083import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 084import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 085import org.apache.directory.server.core.api.interceptor.context.OperationContext; 086import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 087import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 088import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext; 089import org.apache.directory.server.core.api.partition.AbstractPartition; 090import org.apache.directory.server.core.api.partition.Partition; 091import org.apache.directory.server.core.api.partition.PartitionTxn; 092import org.apache.directory.server.core.api.partition.PartitionWriteTxn; 093import org.apache.directory.server.core.api.partition.Subordinates; 094import org.apache.directory.server.i18n.I18n; 095import org.apache.directory.server.xdbm.Index; 096import org.apache.directory.server.xdbm.IndexEntry; 097import org.apache.directory.server.xdbm.IndexNotFoundException; 098import org.apache.directory.server.xdbm.MasterTable; 099import org.apache.directory.server.xdbm.ParentIdAndRdn; 100import org.apache.directory.server.xdbm.Store; 101import org.apache.directory.server.xdbm.search.Optimizer; 102import org.apache.directory.server.xdbm.search.PartitionSearchResult; 103import org.apache.directory.server.xdbm.search.SearchEngine; 104import org.slf4j.Logger; 105import org.slf4j.LoggerFactory; 106 107 108/** 109 * An abstract {@link Partition} that uses general BTree operations. 110 * 111 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 112 */ 113public abstract class AbstractBTreePartition extends AbstractPartition implements Store 114{ 115 /** static logger */ 116 private static final Logger LOG = LoggerFactory.getLogger( AbstractBTreePartition.class ); 117 118 /** the search engine used to search the database */ 119 private SearchEngine searchEngine; 120 121 /** The optimizer to use during search operation */ 122 private Optimizer optimizer; 123 124 /** Tells if the Optimizer is enabled */ 125 protected boolean optimizerEnabled = true; 126 127 /** The default cache size is set to 10 000 objects */ 128 public static final int DEFAULT_CACHE_SIZE = 10000; 129 130 /** The Entry cache size for this partition */ 131 protected int cacheSize = DEFAULT_CACHE_SIZE; 132 133 /** The alias cache */ 134 protected Cache aliasCache; 135 136 /** The ParentIdAndRdn cache */ 137 protected Cache piarCache; 138 139 /** true if we sync disks on every write operation */ 140 protected AtomicBoolean isSyncOnWrite = new AtomicBoolean( true ); 141 142 /** The suffix UUID */ 143 private volatile String suffixId; 144 145 /** The path in which this Partition stores files */ 146 protected URI partitionPath; 147 148 /** The set of indexed attributes */ 149 private Set<Index<?, String>> indexedAttributes; 150 151 /** the master table storing entries by primary key */ 152 protected MasterTable master; 153 154 /** a map of attributeType numeric UUID to user userIndices */ 155 protected Map<String, Index<?, String>> userIndices = new HashMap<>(); 156 157 /** a map of attributeType numeric UUID to system userIndices */ 158 protected Map<String, Index<?, String>> systemIndices = new HashMap<>(); 159 160 /** the relative distinguished name index */ 161 protected Index<ParentIdAndRdn, String> rdnIdx; 162 163 /** a system index on objectClass attribute*/ 164 protected Index<String, String> objectClassIdx; 165 166 /** the attribute presence index */ 167 protected Index<String, String> presenceIdx; 168 169 /** a system index on entryCSN attribute */ 170 protected Index<String, String> entryCsnIdx; 171 172 /** a system index on aliasedObjectName attribute */ 173 protected Index<Dn, String> aliasIdx; 174 175 /** the subtree scope alias index */ 176 protected Index<String, String> subAliasIdx; 177 178 /** the one level scope alias index */ 179 protected Index<String, String> oneAliasIdx; 180 181 /** a system index on administrativeRole attribute */ 182 protected Index<String, String> adminRoleIdx; 183 184 /** Cached attributes types to avoid lookup all over the code */ 185 protected AttributeType objectClassAT; 186 private Normalizer objectClassNormalizer; 187 protected AttributeType presenceAT; 188 private Normalizer presenceNormalizer; 189 protected AttributeType entryCsnAT; 190 protected AttributeType entryDnAT; 191 protected AttributeType entryUuidAT; 192 protected AttributeType aliasedObjectNameAT; 193 protected AttributeType administrativeRoleAT; 194 protected AttributeType contextCsnAT; 195 196 /** Cached value for TOP */ 197 private Value topOCValue; 198 199 private static final boolean NO_REVERSE = Boolean.FALSE; 200 private static final boolean WITH_REVERSE = Boolean.TRUE; 201 202 protected static final boolean ADD_CHILD = true; 203 protected static final boolean REMOVE_CHILD = false; 204 205 /** A lock to protect the backend from concurrent reads/writes */ 206 private ReadWriteLock rwLock; 207 208 /** a cache to hold <entryUUID, Dn> pairs, this is used for speeding up the buildEntryDn() method */ 209 private Cache entryDnCache; 210 211 /** a semaphore to serialize the writes on context entry while updating contextCSN attribute */ 212 private Semaphore ctxCsnSemaphore = new Semaphore( 1 ); 213 214 // ------------------------------------------------------------------------ 215 // C O N S T R U C T O R S 216 // ------------------------------------------------------------------------ 217 218 /** 219 * Creates a B-tree based context partition. 220 * 221 * @param schemaManager the schema manager 222 */ 223 protected AbstractBTreePartition( SchemaManager schemaManager ) 224 { 225 this.schemaManager = schemaManager; 226 227 initInstance(); 228 } 229 230 231 /** 232 * Creates a B-tree based context partition. 233 * 234 * @param schemaManager the schema manager 235 * @param dnFactory the DN factory 236 */ 237 protected AbstractBTreePartition( SchemaManager schemaManager, DnFactory dnFactory ) 238 { 239 this.schemaManager = schemaManager; 240 this.dnFactory = dnFactory; 241 242 initInstance(); 243 } 244 245 246 /** 247 * Intializes the instance. 248 */ 249 private void initInstance() 250 { 251 indexedAttributes = new HashSet<>(); 252 253 // Initialize Attribute types used all over this method 254 objectClassAT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT ); 255 objectClassNormalizer = objectClassAT.getEquality().getNormalizer(); 256 presenceAT = schemaManager.getAttributeType( ApacheSchemaConstants.APACHE_PRESENCE_AT ); 257 presenceNormalizer = presenceAT.getEquality().getNormalizer(); 258 aliasedObjectNameAT = schemaManager.getAttributeType( SchemaConstants.ALIASED_OBJECT_NAME_AT ); 259 entryCsnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_CSN_AT ); 260 entryDnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_DN_AT ); 261 entryUuidAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_UUID_AT ); 262 administrativeRoleAT = schemaManager.getAttributeType( SchemaConstants.ADMINISTRATIVE_ROLE_AT ); 263 contextCsnAT = schemaManager.getAttributeType( SchemaConstants.CONTEXT_CSN_AT ); 264 265 // Initialize a Value for TOP_OC 266 try 267 { 268 topOCValue = new Value( schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT_OID ), SchemaConstants.TOP_OC_OID ); 269 } 270 catch ( LdapInvalidAttributeValueException e ) 271 { 272 // There is nothing we can do... 273 } 274 275 // Relax the entryDnAT so that we don't check the EntryDN twice 276 entryDnAT.setRelaxed( true ); 277 } 278 279 280 // ------------------------------------------------------------------------ 281 // C O N F I G U R A T I O N M E T H O D S 282 // ------------------------------------------------------------------------ 283 /** 284 * Gets the entry cache size for this BTreePartition. 285 * 286 * @return the maximum size of the cache as the number of entries maximum before paging out 287 */ 288 @Override 289 public int getCacheSize() 290 { 291 return cacheSize; 292 } 293 294 295 /** 296 * Used to specify the entry cache size for a Partition. Various Partition 297 * implementations may interpret this value in different ways: i.e. total cache 298 * size limit verses the number of entries to cache. 299 * 300 * @param cacheSize the maximum size of the cache in the number of entries 301 */ 302 @Override 303 public void setCacheSize( int cacheSize ) 304 { 305 this.cacheSize = cacheSize; 306 } 307 308 309 /** 310 * Tells if the Optimizer is enabled or not 311 * @return true if the optimizer is enabled 312 */ 313 public boolean isOptimizerEnabled() 314 { 315 return optimizerEnabled; 316 } 317 318 319 /** 320 * Set the optimizer flag 321 * @param optimizerEnabled The flag 322 */ 323 public void setOptimizerEnabled( boolean optimizerEnabled ) 324 { 325 this.optimizerEnabled = optimizerEnabled; 326 } 327 328 329 /** 330 * Sets the path in which this Partition stores data. This may be an URL to 331 * a file or directory, or an JDBC URL. 332 * 333 * @param partitionPath the path in which this Partition stores data. 334 */ 335 @Override 336 public void setPartitionPath( URI partitionPath ) 337 { 338 checkInitialized( "partitionPath" ); 339 this.partitionPath = partitionPath; 340 } 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override 347 public boolean isSyncOnWrite() 348 { 349 return isSyncOnWrite.get(); 350 } 351 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override 357 public void setSyncOnWrite( boolean isSyncOnWrite ) 358 { 359 checkInitialized( "syncOnWrite" ); 360 this.isSyncOnWrite.set( isSyncOnWrite ); 361 } 362 363 364 /** 365 * Sets up the system indices. 366 * 367 * @throws LdapException If the setup failed 368 */ 369 @SuppressWarnings("unchecked") 370 protected void setupSystemIndices() throws LdapException 371 { 372 // add missing system indices 373 if ( getPresenceIndex() == null ) 374 { 375 Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID, 376 partitionPath, NO_REVERSE ); 377 addIndex( index ); 378 } 379 380 if ( getRdnIndex() == null ) 381 { 382 Index<ParentIdAndRdn, String> index = createSystemIndex( 383 ApacheSchemaConstants.APACHE_RDN_AT_OID, 384 partitionPath, WITH_REVERSE ); 385 addIndex( index ); 386 } 387 388 if ( getAliasIndex() == null ) 389 { 390 Index<Dn, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID, 391 partitionPath, WITH_REVERSE ); 392 addIndex( index ); 393 } 394 395 if ( getOneAliasIndex() == null ) 396 { 397 Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID, 398 partitionPath, NO_REVERSE ); 399 addIndex( index ); 400 } 401 402 if ( getSubAliasIndex() == null ) 403 { 404 Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID, 405 partitionPath, NO_REVERSE ); 406 addIndex( index ); 407 } 408 409 if ( getObjectClassIndex() == null ) 410 { 411 Index<String, String> index = createSystemIndex( SchemaConstants.OBJECT_CLASS_AT_OID, partitionPath, 412 NO_REVERSE ); 413 addIndex( index ); 414 } 415 416 if ( getEntryCsnIndex() == null ) 417 { 418 Index<String, String> index = createSystemIndex( SchemaConstants.ENTRY_CSN_AT_OID, partitionPath, 419 NO_REVERSE ); 420 addIndex( index ); 421 } 422 423 if ( getAdministrativeRoleIndex() == null ) 424 { 425 Index<String, String> index = createSystemIndex( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID, 426 partitionPath, 427 NO_REVERSE ); 428 addIndex( index ); 429 } 430 431 // convert and initialize system indices 432 for ( Map.Entry<String, Index<?, String>> elem : systemIndices.entrySet() ) 433 { 434 Index<?, String> index = elem.getValue(); 435 index = convertAndInit( index ); 436 systemIndices.put( elem.getKey(), index ); 437 } 438 439 // set index shortcuts 440 rdnIdx = ( Index<ParentIdAndRdn, String> ) systemIndices 441 .get( ApacheSchemaConstants.APACHE_RDN_AT_OID ); 442 presenceIdx = ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID ); 443 aliasIdx = ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 444 oneAliasIdx = ( Index<String, String> ) systemIndices 445 .get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID ); 446 subAliasIdx = ( Index<String, String> ) systemIndices 447 .get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID ); 448 objectClassIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID ); 449 entryCsnIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID ); 450 adminRoleIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ); 451 } 452 453 454 /** 455 * Sets up the user indices. 456 * 457 * @throws LdapException If the setup failed 458 */ 459 protected void setupUserIndices() throws LdapException 460 { 461 // convert and initialize system indices 462 Map<String, Index<?, String>> tmp = new HashMap<>(); 463 464 for ( Map.Entry<String, Index<?, String>> elem : userIndices.entrySet() ) 465 { 466 String oid = elem.getKey(); 467 468 // check that the attributeType has an EQUALITY matchingRule 469 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid ); 470 MatchingRule mr = attributeType.getEquality(); 471 472 if ( mr != null ) 473 { 474 Index<?, String> index = elem.getValue(); 475 index = convertAndInit( index ); 476 tmp.put( oid, index ); 477 } 478 else 479 { 480 LOG.error( I18n.err( I18n.ERR_4, attributeType.getName() ) ); 481 } 482 } 483 484 userIndices = tmp; 485 } 486 487 488 /** 489 * Gets the DefaultSearchEngine used by this ContextPartition to search the 490 * Database. 491 * 492 * @return the search engine 493 */ 494 public SearchEngine getSearchEngine() 495 { 496 return searchEngine; 497 } 498 499 500 // ----------------------------------------------------------------------- 501 // Miscellaneous abstract methods 502 // ----------------------------------------------------------------------- 503 /** 504 * Convert and initialize an index for a specific store implementation. 505 * 506 * @param index the index 507 * @return the converted and initialized index 508 * @throws LdapException If teh conversion failed 509 */ 510 protected abstract Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException; 511 512 513 /** 514 * Gets the path in which this Partition stores data. 515 * 516 * @return the path in which this Partition stores data. 517 */ 518 @Override 519 public URI getPartitionPath() 520 { 521 return partitionPath; 522 } 523 524 525 // ------------------------------------------------------------------------ 526 // Partition Interface Method Implementations 527 // ------------------------------------------------------------------------ 528 /** 529 * {@inheritDoc} 530 */ 531 @Override 532 protected void doDestroy( PartitionTxn partitionTxn ) throws LdapException 533 { 534 LOG.debug( "destroy() called on store for {}", this.suffixDn ); 535 536 if ( !initialized ) 537 { 538 return; 539 } 540 541 // don't reset initialized flag 542 initialized = false; 543 544 entryDnCache.removeAll(); 545 546 MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) ); 547 548 for ( Index<?, String> index : userIndices.values() ) 549 { 550 try 551 { 552 index.close( partitionTxn ); 553 LOG.debug( "Closed {} user index for {} partition.", index.getAttributeId(), suffixDn ); 554 } 555 catch ( Throwable t ) 556 { 557 LOG.error( I18n.err( I18n.ERR_124 ), t ); 558 errors.addThrowable( t ); 559 } 560 } 561 562 for ( Index<?, String> index : systemIndices.values() ) 563 { 564 try 565 { 566 index.close( partitionTxn ); 567 LOG.debug( "Closed {} system index for {} partition.", index.getAttributeId(), suffixDn ); 568 } 569 catch ( Throwable t ) 570 { 571 LOG.error( I18n.err( I18n.ERR_124 ), t ); 572 errors.addThrowable( t ); 573 } 574 } 575 576 try 577 { 578 master.close( partitionTxn ); 579 LOG.debug( I18n.err( I18n.ERR_125, suffixDn ) ); 580 } 581 catch ( Throwable t ) 582 { 583 LOG.error( I18n.err( I18n.ERR_126 ), t ); 584 errors.addThrowable( t ); 585 } 586 587 if ( errors.size() > 0 ) 588 { 589 throw new LdapOtherException( errors.getMessage(), errors ); 590 } 591 } 592 593 594 /** 595 * {@inheritDoc} 596 */ 597 @Override 598 public void repair() throws LdapException 599 { 600 // Do nothing by default 601 doRepair(); 602 } 603 604 605 /** 606 * {@inheritDoc} 607 */ 608 @Override 609 protected void doInit() throws LdapException 610 { 611 // First, inject the indexed attributes if any 612 if ( ( indexedAttributes != null ) && ( !indexedAttributes.isEmpty() ) ) 613 { 614 for ( Index index : indexedAttributes ) 615 { 616 addIndex( index ); 617 } 618 } 619 620 // Now, initialize the configured index 621 setupSystemIndices(); 622 setupUserIndices(); 623 624 if ( cacheService != null ) 625 { 626 aliasCache = cacheService.getCache( "alias" ); 627 628 CacheConfiguration cacheConfiguration = aliasCache.getCacheConfiguration(); 629 630 int cacheSizeConfig = ( int ) cacheConfiguration.getMaxEntriesLocalHeap(); 631 632 if ( cacheSizeConfig < cacheSize ) 633 { 634 aliasCache.getCacheConfiguration().setMaxEntriesLocalHeap( cacheSize ); 635 } 636 637 piarCache = cacheService.getCache( "piar" ); 638 639 cacheSizeConfig = ( int ) piarCache.getCacheConfiguration().getMaxEntriesLocalHeap(); 640 641 if ( cacheSizeConfig < cacheSize ) 642 { 643 piarCache.getCacheConfiguration().setMaxEntriesLocalHeap( cacheSize * 3 ); 644 } 645 646 entryDnCache = cacheService.getCache( "entryDn" ); 647 entryDnCache.setMemoryStoreEvictionPolicy( new LruPolicy() ); 648 entryDnCache.getCacheConfiguration().setMaxEntriesLocalHeap( cacheSize ); 649 } 650 } 651 652 653 private void dumpAllRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException 654 { 655 if ( LOG.isDebugEnabled() ) 656 { 657 dumpRdnIdx( partitionTxn, Partition.ROOT_ID, "" ); 658 System.out.println( "-----------------------------" ); 659 } 660 } 661 662 663 private void dumpRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException 664 { 665 if ( LOG.isDebugEnabled() ) 666 { 667 dumpRdnIdx( partitionTxn, Partition.ROOT_ID, 1, "" ); 668 System.out.println( "-----------------------------" ); 669 } 670 } 671 672 673 /** 674 * Dump the RDN index content 675 * 676 * @param partitionTxn The transaction to use 677 * @param id The root ID 678 * @param tabs The space prefix 679 * @throws LdapException If we had an issue while dumping the Rdn index 680 * @throws CursorException If the cursor failed to browse the Rdn Index 681 * @throws IOException If we weren't able to read teh Rdn Index file 682 */ 683 public void dumpRdnIdx( PartitionTxn partitionTxn, String id, String tabs ) throws LdapException, CursorException, IOException 684 { 685 // Start with the root 686 Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn ); 687 688 IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); 689 startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) ); 690 cursor.before( startingPos ); 691 692 while ( cursor.next() ) 693 { 694 IndexEntry<ParentIdAndRdn, String> entry = cursor.get(); 695 System.out.println( tabs + entry ); 696 } 697 698 cursor.close(); 699 } 700 701 702 private void dumpRdnIdx( PartitionTxn partitionTxn, String id, int nbSibbling, String tabs ) 703 throws LdapException, CursorException, IOException 704 { 705 // Start with the root 706 Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn ); 707 708 IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); 709 startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) ); 710 cursor.before( startingPos ); 711 int countChildren = 0; 712 713 while ( cursor.next() && ( countChildren < nbSibbling ) ) 714 { 715 IndexEntry<ParentIdAndRdn, String> entry = cursor.get(); 716 System.out.println( tabs + entry ); 717 countChildren++; 718 719 // And now, the children 720 int nbChildren = entry.getKey().getNbChildren(); 721 722 if ( nbChildren > 0 ) 723 { 724 dumpRdnIdx( partitionTxn, entry.getId(), nbChildren, tabs + " " ); 725 } 726 } 727 728 cursor.close(); 729 } 730 731 732 //--------------------------------------------------------------------------------------------- 733 // The Add operation 734 //--------------------------------------------------------------------------------------------- 735 /** 736 * {@inheritDoc} 737 */ 738 @Override 739 public void add( AddOperationContext addContext ) throws LdapException 740 { 741 PartitionTxn partitionTxn = addContext.getTransaction(); 742 743 assert ( partitionTxn != null ); 744 assert ( partitionTxn instanceof PartitionWriteTxn ); 745 746 try 747 { 748 setRWLock( addContext ); 749 Entry entry = ( ( ClonedServerEntry ) addContext.getEntry() ).getClonedEntry(); 750 751 Dn entryDn = entry.getDn(); 752 753 // check if the entry already exists 754 lockRead(); 755 756 try 757 { 758 if ( getEntryId( partitionTxn, entryDn ) != null ) 759 { 760 throw new LdapEntryAlreadyExistsException( 761 I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, entryDn.getName() ) ); 762 } 763 } 764 finally 765 { 766 unlockRead(); 767 } 768 769 String parentId = null; 770 771 // 772 // Suffix entry cannot have a parent since it is the root so it is 773 // capped off using the zero value which no entry can have since 774 // entry sequences start at 1. 775 // 776 Dn parentDn = null; 777 ParentIdAndRdn key; 778 779 if ( entryDn.getNormName().equals( suffixDn.getNormName() ) ) 780 { 781 parentId = Partition.ROOT_ID; 782 key = new ParentIdAndRdn( parentId, suffixDn.getRdns() ); 783 } 784 else 785 { 786 parentDn = entryDn.getParent(); 787 788 lockRead(); 789 790 try 791 { 792 parentId = getEntryId( partitionTxn, parentDn ); 793 } 794 finally 795 { 796 unlockRead(); 797 } 798 799 key = new ParentIdAndRdn( parentId, entryDn.getRdn() ); 800 } 801 802 // don't keep going if we cannot find the parent Id 803 if ( parentId == null ) 804 { 805 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_216_ID_FOR_PARENT_NOT_FOUND, parentDn ) ); 806 } 807 808 // Get a new UUID for the added entry if it does not have any already 809 Attribute entryUUID = entry.get( entryUuidAT ); 810 811 String id; 812 813 if ( entryUUID == null ) 814 { 815 id = master.getNextId( entry ); 816 } 817 else 818 { 819 id = entryUUID.getString(); 820 } 821 822 if ( entryDn.getNormName().equals( suffixDn.getNormName() ) ) 823 { 824 suffixId = id; 825 } 826 827 // Update the ObjectClass index 828 Attribute objectClass = entry.get( objectClassAT ); 829 830 if ( objectClass == null ) 831 { 832 String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry ); 833 ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION; 834 835 throw new LdapSchemaViolationException( rc, msg ); 836 } 837 838 for ( Value value : objectClass ) 839 { 840 if ( value.equals( topOCValue ) ) 841 { 842 continue; 843 } 844 845 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 846 847 objectClassIdx.add( partitionTxn, normalizedOc, id ); 848 } 849 850 if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) 851 { 852 Attribute aliasAttr = entry.get( aliasedObjectNameAT ); 853 854 addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, aliasAttr.getString() ) ); 855 } 856 857 // Update the EntryCsn index 858 Attribute entryCsn = entry.get( entryCsnAT ); 859 860 if ( entryCsn == null ) 861 { 862 String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry ); 863 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg ); 864 } 865 866 entryCsnIdx.add( partitionTxn, entryCsn.getString(), id ); 867 868 // Update the AdministrativeRole index, if needed 869 if ( entry.containsAttribute( administrativeRoleAT ) ) 870 { 871 // We may have more than one role 872 Attribute adminRoles = entry.get( administrativeRoleAT ); 873 874 for ( Value value : adminRoles ) 875 { 876 adminRoleIdx.add( partitionTxn, value.getValue(), id ); 877 } 878 879 // Adds only those attributes that are indexed 880 presenceIdx.add( partitionTxn, administrativeRoleAT.getOid(), id ); 881 } 882 883 // Now work on the user defined userIndices 884 for ( Attribute attribute : entry ) 885 { 886 AttributeType attributeType = attribute.getAttributeType(); 887 String attributeOid = attributeType.getOid(); 888 889 if ( hasUserIndexOn( attributeType ) ) 890 { 891 Index<Object, String> userIndex = ( Index<Object, String> ) getUserIndex( attributeType ); 892 893 // here lookup by attributeId is OK since we got attributeId from 894 // the entry via the enumeration - it's in there as is for sure 895 896 for ( Value value : attribute ) 897 { 898 String normalized = value.getNormalized(); 899 userIndex.add( partitionTxn, normalized, id ); 900 } 901 902 // Adds only those attributes that are indexed 903 presenceIdx.add( partitionTxn, attributeOid, id ); 904 } 905 } 906 907 // Add the parentId in the entry 908 entry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, parentId ); 909 910 lockWrite(); 911 912 try 913 { 914 // Update the RDN index 915 rdnIdx.add( partitionTxn, key, id ); 916 917 // Update the parent's nbChildren and nbDescendants values 918 if ( parentId != Partition.ROOT_ID ) 919 { 920 updateRdnIdx( partitionTxn, parentId, ADD_CHILD, 0 ); 921 } 922 923 // Remove the EntryDN attribute 924 entry.removeAttributes( entryDnAT ); 925 926 Attribute at = entry.get( SchemaConstants.ENTRY_CSN_AT ); 927 setContextCsn( at.getString() ); 928 929 // And finally add the entry into the master table 930 master.put( partitionTxn, id, entry ); 931 } 932 finally 933 { 934 unlockWrite(); 935 } 936 } 937 catch ( LdapException le ) 938 { 939 throw le; 940 } 941 catch ( Exception e ) 942 { 943 throw new LdapException( e ); 944 } 945 } 946 947 948 //--------------------------------------------------------------------------------------------- 949 // The Delete operation 950 //--------------------------------------------------------------------------------------------- 951 /** 952 * {@inheritDoc} 953 */ 954 @Override 955 public Entry delete( DeleteOperationContext deleteContext ) throws LdapException 956 { 957 PartitionTxn partitionTxn = deleteContext.getTransaction(); 958 959 assert ( partitionTxn != null ); 960 assert ( partitionTxn instanceof PartitionWriteTxn ); 961 962 setRWLock( deleteContext ); 963 Dn dn = deleteContext.getDn(); 964 String id = null; 965 966 lockRead(); 967 968 try 969 { 970 id = getEntryId( partitionTxn, dn ); 971 } 972 finally 973 { 974 unlockRead(); 975 } 976 977 // don't continue if id is null 978 if ( id == null ) 979 { 980 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_699, dn ) ); 981 } 982 983 long childCount = getChildCount( partitionTxn, id ); 984 985 if ( childCount > 0 ) 986 { 987 throw new LdapContextNotEmptyException( I18n.err( I18n.ERR_700, dn ) ); 988 } 989 990 // We now defer the deletion to the implementing class 991 Entry deletedEntry = delete( partitionTxn, id ); 992 993 updateCache( deleteContext ); 994 995 return deletedEntry; 996 } 997 998 999 protected void updateRdnIdx( PartitionTxn partitionTxn, String parentId, boolean addRemove, int nbDescendant ) throws LdapException 1000 { 1001 boolean isFirst = true; 1002 ////dumpRdnIdx(); 1003 1004 if ( parentId.equals( Partition.ROOT_ID ) ) 1005 { 1006 return; 1007 } 1008 1009 ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, parentId ); 1010 1011 while ( parent != null ) 1012 { 1013 rdnIdx.drop( partitionTxn, parentId ); 1014 ////dumpRdnIdx(); 1015 1016 if ( isFirst ) 1017 { 1018 if ( addRemove == ADD_CHILD ) 1019 { 1020 parent.setNbChildren( parent.getNbChildren() + 1 ); 1021 } 1022 else 1023 { 1024 parent.setNbChildren( parent.getNbChildren() - 1 ); 1025 } 1026 1027 isFirst = false; 1028 } 1029 1030 if ( addRemove == ADD_CHILD ) 1031 { 1032 parent.setNbDescendants( parent.getNbDescendants() + ( nbDescendant + 1 ) ); 1033 } 1034 else 1035 { 1036 parent.setNbDescendants( parent.getNbDescendants() - ( nbDescendant + 1 ) ); 1037 } 1038 1039 // Inject the modified element into the index 1040 rdnIdx.add( partitionTxn, parent, parentId ); 1041 1042 ////dumpRdnIdx(); 1043 1044 parentId = parent.getParentId(); 1045 parent = rdnIdx.reverseLookup( partitionTxn, parentId ); 1046 } 1047 } 1048 1049 1050 /** 1051 * Delete the entry associated with a given Id 1052 * 1053 * @param partitionTxn The transaction to use 1054 * @param id The id of the entry to delete 1055 * @return the deleted entry if found 1056 * @throws LdapException If the deletion failed 1057 */ 1058 @Override 1059 public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException 1060 { 1061 try 1062 { 1063 // First get the entry 1064 Entry entry = null; 1065 1066 lockRead(); 1067 1068 try 1069 { 1070 entry = master.get( partitionTxn, id ); 1071 } 1072 finally 1073 { 1074 unlockRead(); 1075 } 1076 1077 if ( entry == null ) 1078 { 1079 // Not allowed 1080 throw new LdapNoSuchObjectException( "Cannot find an entry for UUID " + id ); 1081 } 1082 1083 Attribute objectClass = entry.get( objectClassAT ); 1084 1085 if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) 1086 { 1087 dropAliasIndices( partitionTxn, id ); 1088 } 1089 1090 // Update the ObjectClass index 1091 for ( Value value : objectClass ) 1092 { 1093 if ( value.equals( topOCValue ) ) 1094 { 1095 continue; 1096 } 1097 1098 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1099 1100 objectClassIdx.drop( partitionTxn, normalizedOc, id ); 1101 } 1102 1103 // Update the parent's nbChildren and nbDescendants values 1104 ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, id ); 1105 updateRdnIdx( partitionTxn, parent.getParentId(), REMOVE_CHILD, 0 ); 1106 1107 // Update the rdn, oneLevel, subLevel, and entryCsn indexes 1108 entryCsnIdx.drop( partitionTxn, entry.get( entryCsnAT ).getString(), id ); 1109 1110 // Update the AdministrativeRole index, if needed 1111 if ( entry.containsAttribute( administrativeRoleAT ) ) 1112 { 1113 // We may have more than one role 1114 Attribute adminRoles = entry.get( administrativeRoleAT ); 1115 1116 for ( Value value : adminRoles ) 1117 { 1118 adminRoleIdx.drop( partitionTxn, value.getValue(), id ); 1119 } 1120 1121 // Deletes only those attributes that are indexed 1122 presenceIdx.drop( partitionTxn, administrativeRoleAT.getOid(), id ); 1123 } 1124 1125 // Update the user indexes 1126 for ( Attribute attribute : entry ) 1127 { 1128 AttributeType attributeType = attribute.getAttributeType(); 1129 String attributeOid = attributeType.getOid(); 1130 1131 if ( hasUserIndexOn( attributeType ) ) 1132 { 1133 Index<?, String> userIndex = getUserIndex( attributeType ); 1134 1135 // here lookup by attributeId is ok since we got attributeId from 1136 // the entry via the enumeration - it's in there as is for sure 1137 for ( Value value : attribute ) 1138 { 1139 String normalized = value.getNormalized(); 1140 ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); 1141 } 1142 1143 presenceIdx.drop( partitionTxn, attributeOid, id ); 1144 } 1145 } 1146 1147 lockWrite(); 1148 1149 try 1150 { 1151 rdnIdx.drop( partitionTxn, id ); 1152 1153 ////dumpRdnIdx(); 1154 1155 entryDnCache.remove( id ); 1156 1157 Attribute csn = entry.get( entryCsnAT ); 1158 // can be null while doing subentry deletion 1159 if ( csn != null ) 1160 { 1161 setContextCsn( csn.getString() ); 1162 } 1163 1164 master.remove( partitionTxn, id ); 1165 } 1166 finally 1167 { 1168 unlockWrite(); 1169 } 1170 1171 if ( isSyncOnWrite.get() ) 1172 { 1173 sync(); 1174 } 1175 1176 return entry; 1177 } 1178 catch ( Exception e ) 1179 { 1180 throw new LdapOperationErrorException( e.getMessage(), e ); 1181 } 1182 } 1183 1184 1185 //--------------------------------------------------------------------------------------------- 1186 // The Search operation 1187 //--------------------------------------------------------------------------------------------- 1188 /** 1189 * {@inheritDoc} 1190 */ 1191 @Override 1192 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 1193 { 1194 PartitionTxn partitionTxn = searchContext.getTransaction(); 1195 1196 assert ( partitionTxn != null ); 1197 1198 try 1199 { 1200 setRWLock( searchContext ); 1201 1202 if ( ctxCsnChanged && getSuffixDn().equals( searchContext.getDn() ) ) 1203 { 1204 try 1205 { 1206 ctxCsnSemaphore.acquire(); 1207 saveContextCsn( partitionTxn ); 1208 } 1209 catch ( Exception e ) 1210 { 1211 throw new LdapOperationErrorException( e.getMessage(), e ); 1212 } 1213 finally 1214 { 1215 ctxCsnSemaphore.release(); 1216 } 1217 } 1218 1219 PartitionSearchResult searchResult = searchEngine.computeResult( partitionTxn, schemaManager, searchContext ); 1220 1221 Cursor<Entry> result = new EntryCursorAdaptor( partitionTxn, this, searchResult ); 1222 1223 return new EntryFilteringCursorImpl( result, searchContext, schemaManager ); 1224 } 1225 catch ( LdapException le ) 1226 { 1227 // TODO: SearchEngine.cursor() should only throw LdapException, then the exception handling here can be removed 1228 throw le; 1229 } 1230 catch ( Exception e ) 1231 { 1232 throw new LdapOperationErrorException( e.getMessage(), e ); 1233 } 1234 } 1235 1236 1237 //--------------------------------------------------------------------------------------------- 1238 // The Lookup operation 1239 //--------------------------------------------------------------------------------------------- 1240 /** 1241 * {@inheritDoc} 1242 */ 1243 @Override 1244 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 1245 { 1246 PartitionTxn partitionTxn = lookupContext.getTransaction(); 1247 1248 assert ( partitionTxn != null ); 1249 1250 try 1251 { 1252 setRWLock( lookupContext ); 1253 String id = getEntryId( partitionTxn, lookupContext.getDn() ); 1254 1255 if ( id == null ) 1256 { 1257 return null; 1258 } 1259 1260 if ( ctxCsnChanged && getSuffixDn().getNormName().equals( lookupContext.getDn().getNormName() ) ) 1261 { 1262 try 1263 { 1264 ctxCsnSemaphore.acquire(); 1265 saveContextCsn( partitionTxn ); 1266 } 1267 catch ( Exception e ) 1268 { 1269 throw new LdapOperationErrorException( e.getMessage(), e ); 1270 } 1271 finally 1272 { 1273 ctxCsnSemaphore.release(); 1274 } 1275 } 1276 1277 return fetch( partitionTxn, id, lookupContext.getDn() ); 1278 } 1279 catch ( Exception e ) 1280 { 1281 throw new LdapOperationErrorException( e.getMessage() ); 1282 } 1283 } 1284 1285 1286 /** 1287 * Get back an entry knowing its UUID 1288 * 1289 * @param partitionTxn The transaction to use 1290 * @param id The Entry UUID we want to get back 1291 * @return The found Entry, or null if not found 1292 * @throws LdapException If the lookup failed for any reason (except a not found entry) 1293 */ 1294 @Override 1295 public Entry fetch( PartitionTxn partitionTxn, String id ) throws LdapException 1296 { 1297 try 1298 { 1299 rwLock.readLock().lock(); 1300 1301 Dn dn = buildEntryDn( partitionTxn, id ); 1302 1303 return fetch( partitionTxn, id, dn ); 1304 } 1305 catch ( Exception e ) 1306 { 1307 throw new LdapOperationErrorException( e.getMessage(), e ); 1308 } 1309 finally 1310 { 1311 rwLock.readLock().unlock(); 1312 } 1313 } 1314 1315 1316 /** 1317 * Get back an entry knowing its UUID 1318 * 1319 * @param partitionTxn The transaction to use 1320 * @param id The Entry UUID we want to get back 1321 * @return The found Entry, or null if not found 1322 * @throws LdapException If the lookup failed for any reason (except a not found entry) 1323 */ 1324 @Override 1325 public Entry fetch( PartitionTxn partitionTxn, String id, Dn dn ) throws LdapException 1326 { 1327 try 1328 { 1329 Entry entry = lookupCache( id ); 1330 1331 if ( entry != null ) 1332 { 1333 entry.setDn( dn ); 1334 1335 entry = new ClonedServerEntry( entry ); 1336 1337 // Replace the entry's DN with the provided one 1338 Attribute entryDnAt = entry.get( entryDnAT ); 1339 Value dnValue = new Value( entryDnAT, dn.getName(), dn.getNormName() ); 1340 1341 if ( entryDnAt == null ) 1342 { 1343 entry.add( entryDnAT, dnValue ); 1344 } 1345 else 1346 { 1347 entryDnAt.clear(); 1348 entryDnAt.add( dnValue ); 1349 } 1350 1351 return entry; 1352 } 1353 1354 try 1355 { 1356 rwLock.readLock().lock(); 1357 entry = master.get( partitionTxn, id ); 1358 } 1359 finally 1360 { 1361 rwLock.readLock().unlock(); 1362 } 1363 1364 if ( entry != null ) 1365 { 1366 // We have to store the DN in this entry 1367 entry.setDn( dn ); 1368 1369 // always store original entry in the cache 1370 addToCache( id, entry ); 1371 1372 entry = new ClonedServerEntry( entry ); 1373 1374 if ( !entry.containsAttribute( entryDnAT ) ) 1375 { 1376 entry.add( entryDnAT, dn.getName() ); 1377 } 1378 1379 return entry; 1380 } 1381 1382 return null; 1383 } 1384 catch ( Exception e ) 1385 { 1386 throw new LdapOperationErrorException( e.getMessage(), e ); 1387 } 1388 } 1389 1390 1391 //--------------------------------------------------------------------------------------------- 1392 // The Modify operation 1393 //--------------------------------------------------------------------------------------------- 1394 /** 1395 * {@inheritDoc} 1396 */ 1397 @Override 1398 public void modify( ModifyOperationContext modifyContext ) throws LdapException 1399 { 1400 PartitionTxn partitionTxn = modifyContext.getTransaction(); 1401 1402 assert ( partitionTxn != null ); 1403 assert ( partitionTxn instanceof PartitionWriteTxn ); 1404 1405 try 1406 { 1407 setRWLock( modifyContext ); 1408 1409 Entry modifiedEntry = modify( partitionTxn, modifyContext.getDn(), 1410 modifyContext.getModItems().toArray( new Modification[] 1411 {} ) ); 1412 1413 modifyContext.setAlteredEntry( modifiedEntry ); 1414 1415 updateCache( modifyContext ); 1416 } 1417 catch ( Exception e ) 1418 { 1419 throw new LdapOperationErrorException( e.getMessage(), e ); 1420 } 1421 } 1422 1423 1424 /** 1425 * {@inheritDoc} 1426 */ 1427 @Override 1428 public final synchronized Entry modify( PartitionTxn partitionTxn, Dn dn, Modification... mods ) throws LdapException 1429 { 1430 String id = getEntryId( partitionTxn, dn ); 1431 Entry entry = master.get( partitionTxn, id ); 1432 1433 for ( Modification mod : mods ) 1434 { 1435 Attribute attrMods = mod.getAttribute(); 1436 1437 try 1438 { 1439 switch ( mod.getOperation() ) 1440 { 1441 case ADD_ATTRIBUTE: 1442 modifyAdd( partitionTxn, id, entry, attrMods ); 1443 break; 1444 1445 case REMOVE_ATTRIBUTE: 1446 modifyRemove( partitionTxn, id, entry, attrMods ); 1447 break; 1448 1449 case REPLACE_ATTRIBUTE: 1450 modifyReplace( partitionTxn, id, entry, attrMods ); 1451 break; 1452 1453 default: 1454 throw new LdapException( I18n.err( I18n.ERR_221 ) ); 1455 } 1456 } 1457 catch ( IndexNotFoundException infe ) 1458 { 1459 throw new LdapOtherException( infe.getMessage(), infe ); 1460 } 1461 } 1462 1463 updateCsnIndex( partitionTxn, entry, id ); 1464 1465 // Remove the EntryDN 1466 entry.removeAttributes( entryDnAT ); 1467 1468 setContextCsn( entry.get( entryCsnAT ).getString() ); 1469 1470 master.put( partitionTxn, id, entry ); 1471 1472 return entry; 1473 } 1474 1475 1476 /** 1477 * Adds a set of attribute values while affecting the appropriate userIndices. 1478 * The entry is not persisted: it is only changed in anticipation for a put 1479 * into the master table. 1480 * 1481 * @param partitionTxn The transaction to use 1482 * @param id the primary key of the entry 1483 * @param entry the entry to alter 1484 * @param mods the attribute and values to add 1485 * @throws Exception if index alteration or attribute addition fails 1486 */ 1487 @SuppressWarnings("unchecked") 1488 private void modifyAdd( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 1489 throws LdapException, IndexNotFoundException 1490 { 1491 if ( entry instanceof ClonedServerEntry ) 1492 { 1493 throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); 1494 } 1495 1496 String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); 1497 String normalizedModsOid = presenceNormalizer.normalize( modsOid ); 1498 1499 AttributeType attributeType = mods.getAttributeType(); 1500 1501 // Special case for the ObjectClass index 1502 if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) ) 1503 { 1504 for ( Value value : mods ) 1505 { 1506 if ( value.equals( topOCValue ) ) 1507 { 1508 continue; 1509 } 1510 1511 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1512 1513 objectClassIdx.add( partitionTxn, normalizedOc, id ); 1514 } 1515 } 1516 else if ( hasUserIndexOn( attributeType ) ) 1517 { 1518 Index<?, String> userIndex = getUserIndex( attributeType ); 1519 1520 if ( mods.size() > 0 ) 1521 { 1522 for ( Value value : mods ) 1523 { 1524 String normalized = value.getNormalized(); 1525 ( ( Index ) userIndex ).add( partitionTxn, normalized, id ); 1526 } 1527 } 1528 else 1529 { 1530 // Special case when we have null values 1531 ( ( Index ) userIndex ).add( partitionTxn, null, id ); 1532 } 1533 1534 // If the attr didn't exist for this id add it to presence index 1535 if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) ) 1536 { 1537 presenceIdx.add( partitionTxn, normalizedModsOid, id ); 1538 } 1539 } 1540 // Special case for the AdministrativeRole index 1541 else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) ) 1542 { 1543 // We may have more than one role 1544 for ( Value value : mods ) 1545 { 1546 adminRoleIdx.add( partitionTxn, value.getValue(), id ); 1547 } 1548 1549 // If the attr didn't exist for this id add it to presence index 1550 if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) ) 1551 { 1552 presenceIdx.add( partitionTxn, normalizedModsOid, id ); 1553 } 1554 } 1555 1556 // add all the values in mods to the same attribute in the entry 1557 if ( mods.size() > 0 ) 1558 { 1559 for ( Value value : mods ) 1560 { 1561 entry.add( mods.getAttributeType(), value ); 1562 } 1563 } 1564 else 1565 { 1566 // Special cases for null values 1567 if ( mods.getAttributeType().getSyntax().isHumanReadable() ) 1568 { 1569 entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( String ) null ) ); 1570 } 1571 else 1572 { 1573 entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( byte[] ) null ) ); 1574 } 1575 } 1576 1577 if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) ) 1578 { 1579 Dn ndn = getEntryDn( partitionTxn, id ); 1580 addAliasIndices( partitionTxn, id, ndn, new Dn( schemaManager, mods.getString() ) ); 1581 } 1582 } 1583 1584 1585 /** 1586 * Completely replaces the existing set of values for an attribute with the 1587 * modified values supplied affecting the appropriate userIndices. The entry 1588 * is not persisted: it is only changed in anticipation for a put into the 1589 * master table. 1590 * 1591 * @param partitionTxn The transaction to use 1592 * @param id the primary key of the entry 1593 * @param entry the entry to alter 1594 * @param mods the replacement attribute and values 1595 * @throws Exception if index alteration or attribute modification 1596 * fails. 1597 */ 1598 @SuppressWarnings("unchecked") 1599 private void modifyReplace( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 1600 throws LdapException, IndexNotFoundException 1601 { 1602 if ( entry instanceof ClonedServerEntry ) 1603 { 1604 throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); 1605 } 1606 1607 String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); 1608 AttributeType attributeType = mods.getAttributeType(); 1609 1610 // Special case for the ObjectClass index 1611 if ( attributeType.equals( objectClassAT ) ) 1612 { 1613 // if the id exists in the index drop all existing attribute 1614 // value index entries and add new ones 1615 for ( Value value : entry.get( objectClassAT ) ) 1616 { 1617 if ( value.equals( topOCValue ) ) 1618 { 1619 continue; 1620 } 1621 1622 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1623 1624 objectClassIdx.drop( partitionTxn, normalizedOc, id ); 1625 } 1626 1627 for ( Value value : mods ) 1628 { 1629 if ( value.equals( topOCValue ) ) 1630 { 1631 continue; 1632 } 1633 1634 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1635 1636 objectClassIdx.add( partitionTxn, normalizedOc, id ); 1637 } 1638 } 1639 else if ( hasUserIndexOn( attributeType ) ) 1640 { 1641 Index<?, String> userIndex = getUserIndex( attributeType ); 1642 1643 // Drop all the previous values 1644 Attribute oldAttribute = entry.get( mods.getAttributeType() ); 1645 1646 if ( oldAttribute != null ) 1647 { 1648 for ( Value value : oldAttribute ) 1649 { 1650 String normalized = value.getNormalized(); 1651 ( ( Index<Object, String> ) userIndex ).drop( partitionTxn, normalized, id ); 1652 } 1653 } 1654 1655 // And add the new ones 1656 for ( Value value : mods ) 1657 { 1658 String normalized = value.getNormalized(); 1659 ( ( Index ) userIndex ).add( partitionTxn, normalized, id ); 1660 } 1661 1662 /* 1663 * If we have no new value, we have to drop the AT fro the presence index 1664 */ 1665 if ( mods.size() == 0 ) 1666 { 1667 presenceIdx.drop( partitionTxn, modsOid, id ); 1668 } 1669 } 1670 // Special case for the AdministrativeRole index 1671 else if ( attributeType.equals( administrativeRoleAT ) ) 1672 { 1673 // Remove the previous values 1674 for ( Value value : entry.get( administrativeRoleAT ) ) 1675 { 1676 if ( value.equals( topOCValue ) ) 1677 { 1678 continue; 1679 } 1680 1681 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1682 1683 objectClassIdx.drop( partitionTxn, normalizedOc, id ); 1684 } 1685 1686 // And add the new ones 1687 for ( Value value : mods ) 1688 { 1689 String valueStr = value.getValue(); 1690 1691 if ( valueStr.equals( topOCValue ) ) 1692 { 1693 continue; 1694 } 1695 1696 adminRoleIdx.add( partitionTxn, valueStr, id ); 1697 } 1698 } 1699 1700 String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName( 1701 SchemaConstants.ALIASED_OBJECT_NAME_AT ); 1702 1703 if ( mods.getAttributeType().equals( aliasedObjectNameAT ) ) 1704 { 1705 dropAliasIndices( partitionTxn, id ); 1706 } 1707 1708 // replaces old attributes with new modified ones if they exist 1709 if ( mods.size() > 0 ) 1710 { 1711 entry.put( mods ); 1712 } 1713 else 1714 // removes old attributes if new replacements do not exist 1715 { 1716 entry.remove( mods ); 1717 } 1718 1719 if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 ) 1720 { 1721 Dn entryDn = getEntryDn( partitionTxn, id ); 1722 addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, mods.getString() ) ); 1723 } 1724 } 1725 1726 1727 /** 1728 * Completely removes the set of values for an attribute having the values 1729 * supplied while affecting the appropriate userIndices. The entry is not 1730 * persisted: it is only changed in anticipation for a put into the master 1731 * table. Note that an empty attribute w/o values will remove all the 1732 * values within the entry where as an attribute w/ values will remove those 1733 * attribute values it contains. 1734 * 1735 * @param partitionTxn The transaction to use 1736 * @param id the primary key of the entry 1737 * @param entry the entry to alter 1738 * @param mods the attribute and its values to delete 1739 * @throws Exception if index alteration or attribute modification fails. 1740 */ 1741 @SuppressWarnings("unchecked") 1742 private void modifyRemove( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 1743 throws LdapException, IndexNotFoundException 1744 { 1745 if ( entry instanceof ClonedServerEntry ) 1746 { 1747 throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); 1748 } 1749 1750 String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); 1751 AttributeType attributeType = mods.getAttributeType(); 1752 1753 // Special case for the ObjectClass index 1754 if ( attributeType.equals( objectClassAT ) ) 1755 { 1756 /* 1757 * If there are no attribute values in the modifications then this 1758 * implies the complete removal of the attribute from the index. Else 1759 * we remove individual tuples from the index. 1760 */ 1761 if ( mods.size() == 0 ) 1762 { 1763 for ( Value value : entry.get( objectClassAT ) ) 1764 { 1765 if ( value.equals( topOCValue ) ) 1766 { 1767 continue; 1768 } 1769 1770 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1771 1772 objectClassIdx.drop( partitionTxn, normalizedOc, id ); 1773 } 1774 } 1775 else 1776 { 1777 for ( Value value : mods ) 1778 { 1779 if ( value.equals( topOCValue ) ) 1780 { 1781 continue; 1782 } 1783 1784 String normalizedOc = objectClassNormalizer.normalize( value.getValue() ); 1785 1786 objectClassIdx.drop( partitionTxn, normalizedOc, id ); 1787 } 1788 } 1789 } 1790 else if ( hasUserIndexOn( attributeType ) ) 1791 { 1792 Index<?, String> userIndex = getUserIndex( attributeType ); 1793 1794 Attribute attribute = entry.get( attributeType ).clone(); 1795 int nbValues = 0; 1796 1797 if ( attribute != null ) 1798 { 1799 nbValues = attribute.size(); 1800 } 1801 1802 /* 1803 * If there are no attribute values in the modifications then this 1804 * implies the complete removal of the attribute from the index. Else 1805 * we remove individual tuples from the index. 1806 */ 1807 if ( mods.size() == 0 ) 1808 { 1809 ( ( Index ) userIndex ).drop( partitionTxn, id ); 1810 nbValues = 0; 1811 } 1812 else 1813 { 1814 for ( Value value : mods ) 1815 { 1816 if ( attribute.contains( value ) ) 1817 { 1818 nbValues--; 1819 attribute.remove( value ); 1820 } 1821 1822 String normalized = value.getNormalized(); 1823 ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); 1824 } 1825 } 1826 1827 /* 1828 * If no attribute values exist for this entryId in the index then 1829 * we remove the presence index entry for the removed attribute. 1830 */ 1831 if ( nbValues == 0 ) 1832 { 1833 presenceIdx.drop( partitionTxn, modsOid, id ); 1834 } 1835 } 1836 // Special case for the AdministrativeRole index 1837 else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) ) 1838 { 1839 // We may have more than one role 1840 for ( Value value : mods ) 1841 { 1842 adminRoleIdx.drop( partitionTxn, value.getValue(), id ); 1843 } 1844 1845 /* 1846 * If no attribute values exist for this entryId in the index then 1847 * we remove the presence index entry for the removed attribute. 1848 */ 1849 if ( null == adminRoleIdx.reverseLookup( partitionTxn, id ) ) 1850 { 1851 presenceIdx.drop( partitionTxn, modsOid, id ); 1852 } 1853 } 1854 1855 /* 1856 * If there are no attribute values in the modifications then this 1857 * implies the complete removal of the attribute from the entry. Else 1858 * we remove individual attribute values from the entry in mods one 1859 * at a time. 1860 */ 1861 if ( mods.size() == 0 ) 1862 { 1863 entry.removeAttributes( mods.getAttributeType() ); 1864 } 1865 else 1866 { 1867 Attribute entryAttr = entry.get( mods.getAttributeType() ); 1868 1869 for ( Value value : mods ) 1870 { 1871 entryAttr.remove( value ); 1872 } 1873 1874 // if nothing is left just remove empty attribute 1875 if ( entryAttr.size() == 0 ) 1876 { 1877 entry.removeAttributes( entryAttr.getId() ); 1878 } 1879 } 1880 1881 // Aliases->single valued comp/partial attr removal is not relevant here 1882 if ( mods.getAttributeType().equals( aliasedObjectNameAT ) ) 1883 { 1884 dropAliasIndices( partitionTxn, id ); 1885 } 1886 } 1887 1888 1889 //--------------------------------------------------------------------------------------------- 1890 // The Move operation 1891 //--------------------------------------------------------------------------------------------- 1892 /** 1893 * {@inheritDoc} 1894 */ 1895 @Override 1896 public void move( MoveOperationContext moveContext ) throws LdapException 1897 { 1898 if ( moveContext.getNewSuperior().isDescendantOf( moveContext.getDn() ) ) 1899 { 1900 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 1901 "cannot place an entry below itself" ); 1902 } 1903 1904 PartitionTxn partitionTxn = moveContext.getTransaction(); 1905 1906 assert ( partitionTxn != null ); 1907 assert ( partitionTxn instanceof PartitionWriteTxn ); 1908 1909 try 1910 { 1911 setRWLock( moveContext ); 1912 Dn oldDn = moveContext.getDn(); 1913 Dn newSuperior = moveContext.getNewSuperior(); 1914 Dn newDn = moveContext.getNewDn(); 1915 Entry modifiedEntry = moveContext.getModifiedEntry(); 1916 1917 move( partitionTxn, oldDn, newSuperior, newDn, modifiedEntry ); 1918 updateCache( moveContext ); 1919 } 1920 catch ( Exception e ) 1921 { 1922 throw new LdapOperationErrorException( e.getMessage(), e ); 1923 } 1924 } 1925 1926 1927 /** 1928 * {@inheritDoc} 1929 */ 1930 @Override 1931 public final synchronized void move( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Dn newDn, Entry modifiedEntry ) 1932 throws LdapException 1933 { 1934 // Check that the parent Dn exists 1935 String newParentId = getEntryId( partitionTxn, newSuperiorDn ); 1936 1937 if ( newParentId == null ) 1938 { 1939 // This is not allowed : the parent must exist 1940 throw new LdapEntryAlreadyExistsException( 1941 I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn.getName() ) ); 1942 } 1943 1944 // Now check that the new entry does not exist 1945 String newId = getEntryId( partitionTxn, newDn ); 1946 1947 if ( newId != null ) 1948 { 1949 // This is not allowed : we should not be able to move an entry 1950 // to an existing position 1951 throw new LdapEntryAlreadyExistsException( 1952 I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) ); 1953 } 1954 1955 // Get the entry and the old parent IDs 1956 String entryId = getEntryId( partitionTxn, oldDn ); 1957 String oldParentId = getParentId( partitionTxn, entryId ); 1958 1959 /* 1960 * All aliases including and below oldChildDn, will be affected by 1961 * the move operation with respect to one and subtree userIndices since 1962 * their relationship to ancestors above oldChildDn will be 1963 * destroyed. For each alias below and including oldChildDn we will 1964 * drop the index tuples mapping ancestor ids above oldChildDn to the 1965 * respective target ids of the aliases. 1966 */ 1967 dropMovedAliasIndices( partitionTxn, oldDn ); 1968 1969 // Update the Rdn index 1970 // First drop the old entry 1971 ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId ); 1972 1973 updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() ); 1974 1975 rdnIdx.drop( partitionTxn, entryId ); 1976 1977 // Now, add the new entry at the right position 1978 movedEntry.setParentId( newParentId ); 1979 rdnIdx.add( partitionTxn, movedEntry, entryId ); 1980 1981 updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() ); 1982 1983 /* 1984 * Read Alias Index Tuples 1985 * 1986 * If this is a name change due to a move operation then the one and 1987 * subtree userIndices for aliases were purged before the aliases were 1988 * moved. Now we must add them for each alias entry we have moved. 1989 * 1990 * aliasTarget is used as a marker to tell us if we're moving an 1991 * alias. If it is null then the moved entry is not an alias. 1992 */ 1993 Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId ); 1994 1995 if ( null != aliasTarget ) 1996 { 1997 if ( !aliasTarget.isSchemaAware() ) 1998 { 1999 aliasTarget = new Dn( schemaManager, aliasTarget ); 2000 } 2001 2002 2003 addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget ); 2004 } 2005 2006 // the below case arises only when the move( Dn oldDn, Dn newSuperiorDn, Dn newDn ) is called 2007 // directly using the Store API, in this case the value of modified entry will be null 2008 // we need to lookup the entry to update the parent UUID 2009 if ( modifiedEntry == null ) 2010 { 2011 modifiedEntry = fetch( partitionTxn, entryId ); 2012 } 2013 2014 // Update the master table with the modified entry 2015 modifiedEntry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, newParentId ); 2016 2017 // Remove the EntryDN 2018 modifiedEntry.removeAttributes( entryDnAT ); 2019 2020 entryDnCache.removeAll(); 2021 2022 setContextCsn( modifiedEntry.get( entryCsnAT ).getString() ); 2023 2024 master.put( partitionTxn, entryId, modifiedEntry ); 2025 2026 if ( isSyncOnWrite.get() ) 2027 { 2028 sync(); 2029 } 2030 } 2031 2032 2033 //--------------------------------------------------------------------------------------------- 2034 // The MoveAndRename operation 2035 //--------------------------------------------------------------------------------------------- 2036 /** 2037 * {@inheritDoc} 2038 */ 2039 @Override 2040 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 2041 { 2042 if ( moveAndRenameContext.getNewSuperiorDn().isDescendantOf( moveAndRenameContext.getDn() ) ) 2043 { 2044 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 2045 "cannot place an entry below itself" ); 2046 } 2047 2048 PartitionTxn partitionTxn = moveAndRenameContext.getTransaction(); 2049 2050 assert ( partitionTxn != null ); 2051 assert ( partitionTxn instanceof PartitionWriteTxn ); 2052 2053 try 2054 { 2055 setRWLock( moveAndRenameContext ); 2056 Dn oldDn = moveAndRenameContext.getDn(); 2057 Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn(); 2058 Rdn newRdn = moveAndRenameContext.getNewRdn(); 2059 Entry modifiedEntry = moveAndRenameContext.getModifiedEntry(); 2060 Map<String, List<ModDnAva>> modAvas = moveAndRenameContext.getModifiedAvas(); 2061 2062 moveAndRename( partitionTxn, oldDn, newSuperiorDn, newRdn, modAvas, modifiedEntry ); 2063 updateCache( moveAndRenameContext ); 2064 } 2065 catch ( LdapException le ) 2066 { 2067 // In case we get an LdapException, just rethrow it as is to 2068 // avoid having it lost 2069 throw le; 2070 } 2071 catch ( Exception e ) 2072 { 2073 throw new LdapOperationErrorException( e.getMessage(), e ); 2074 } 2075 } 2076 2077 2078 /** 2079 * Moves an entry under a new parent. The operation causes a shift in the 2080 * parent child relationships between the old parent, new parent and the 2081 * child moved. All other descendant entries under the child never change 2082 * their direct parent child relationships. Hence after the parent child 2083 * relationship changes are broken at the old parent and set at the new 2084 * parent a modifyDn operation is conducted to handle name changes 2085 * propagating down through the moved child and its descendants. 2086 * 2087 * @param oldDn the normalized dn of the child to be moved 2088 * @param newSuperiorDn the id of the child being moved 2089 * @param newRdn the normalized dn of the new parent for the child 2090 * @param modAvas The modified Avas 2091 * @param modifiedEntry the modified entry 2092 * @throws LdapException if something goes wrong 2093 */ 2094 @Override 2095 public void moveAndRename( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Rdn newRdn, Map<String, 2096 List<ModDnAva>> modAvas, Entry modifiedEntry ) throws LdapException 2097 { 2098 // Get the child and the new parent to be entries and Ids 2099 Attribute entryIdAt = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT ); 2100 String entryId; 2101 2102 if ( entryIdAt == null ) 2103 { 2104 entryId = getEntryId( partitionTxn, modifiedEntry.getDn() ); 2105 } 2106 else 2107 { 2108 entryId = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT ).getString(); 2109 } 2110 2111 Attribute oldParentIdAt = modifiedEntry.get( ApacheSchemaConstants.ENTRY_PARENT_ID_AT ); 2112 String oldParentId; 2113 2114 if ( oldParentIdAt == null ) 2115 { 2116 oldParentId = getEntryId( partitionTxn, oldDn.getParent() ); 2117 } 2118 else 2119 { 2120 oldParentId = oldParentIdAt.getString(); 2121 } 2122 2123 String newParentId = getEntryId( partitionTxn, newSuperiorDn ); 2124 2125 //Get the info about the moved entry 2126 ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId ); 2127 2128 // First drop the moved entry from the rdn index 2129 rdnIdx.drop( partitionTxn, entryId ); 2130 2131 // 2132 // The update the Rdn index. We will remove the ParentIdAndRdn associated with the 2133 // moved entry, and update the nbChilden of its parent and the nbSubordinates 2134 // of all its ascendant, up to the common superior. 2135 // Then we will add a ParentidAndRdn for the moved entry under the new superior, 2136 // update its children number and the nbSubordinates of all the new ascendant. 2137 updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() ); 2138 2139 /* 2140 * All aliases including and below oldChildDn, will be affected by 2141 * the move operation with respect to one and subtree userIndices since 2142 * their relationship to ancestors above oldChildDn will be 2143 * destroyed. For each alias below and including oldChildDn we will 2144 * drop the index tuples mapping ancestor ids above oldChildDn to the 2145 * respective target ids of the aliases. 2146 */ 2147 dropMovedAliasIndices( partitionTxn, oldDn ); 2148 2149 // Now, add the new entry at the right position 2150 // First 2151 movedEntry.setParentId( newParentId ); 2152 movedEntry.setRdns( new Rdn[] 2153 { newRdn } ); 2154 rdnIdx.add( partitionTxn, movedEntry, entryId ); 2155 2156 updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() ); 2157 2158 // Process the modified indexes now 2159 try 2160 { 2161 processModifiedAvas( partitionTxn, modAvas, entryId ); 2162 } 2163 catch ( IndexNotFoundException infe ) 2164 { 2165 throw new LdapOtherException( infe.getMessage(), infe ); 2166 } 2167 2168 /* 2169 * Read Alias Index Tuples 2170 * 2171 * If this is a name change due to a move operation then the one and 2172 * subtree userIndices for aliases were purged before the aliases were 2173 * moved. Now we must add them for each alias entry we have moved. 2174 * 2175 * aliasTarget is used as a marker to tell us if we're moving an 2176 * alias. If it is null then the moved entry is not an alias. 2177 */ 2178 Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId ); 2179 2180 if ( null != aliasTarget ) 2181 { 2182 if ( !aliasTarget.isSchemaAware() ) 2183 { 2184 aliasTarget = new Dn( schemaManager, aliasTarget ); 2185 } 2186 2187 addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget ); 2188 } 2189 2190 // Remove the EntryDN 2191 modifiedEntry.removeAttributes( entryDnAT ); 2192 2193 // Update the entryParentId attribute 2194 modifiedEntry.removeAttributes( ApacheSchemaConstants.ENTRY_PARENT_ID_OID ); 2195 modifiedEntry.add( ApacheSchemaConstants.ENTRY_PARENT_ID_OID, newParentId ); 2196 2197 // Doom the DN cache now 2198 entryDnCache.removeAll(); 2199 2200 setContextCsn( modifiedEntry.get( entryCsnAT ).getString() ); 2201 2202 // save the modified entry at the new place 2203 master.put( partitionTxn, entryId, modifiedEntry ); 2204 } 2205 2206 2207 /** 2208 * Update the index accordingly to the changed Attribute in the old and new RDN 2209 * 2210 * @param partitionTxn The transaction to use 2211 * @param modAvs The modified AVAs 2212 * @param entryId The Entry ID 2213 * @throws {@link LdapException} If the AVA cannt be processed properly 2214 * @throws IndexNotFoundException If teh index is not found 2215 */ 2216 private void processModifiedAvas( PartitionTxn partitionTxn, Map<String, List<ModDnAva>> modAvas, String entryId ) 2217 throws LdapException, IndexNotFoundException 2218 { 2219 for ( List<ModDnAva> modDnAvas : modAvas.values() ) 2220 { 2221 for ( ModDnAva modDnAva : modDnAvas ) 2222 { 2223 AttributeType attributeType = modDnAva.getAva().getAttributeType(); 2224 2225 if ( !hasIndexOn( attributeType ) ) 2226 { 2227 break; 2228 } 2229 2230 Index<?, String> index = getUserIndex( attributeType ); 2231 2232 switch ( modDnAva.getType() ) 2233 { 2234 case ADD : 2235 case UPDATE_ADD : 2236 // Add Value in the index 2237 ( ( Index ) index ).add( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId ); 2238 2239 /* 2240 * If there is no value for id in this index due to our 2241 * add above we add the entry in the presence idx 2242 */ 2243 if ( null == index.reverseLookup( partitionTxn, entryId ) ) 2244 { 2245 presenceIdx.add( partitionTxn, attributeType.getOid(), entryId ); 2246 } 2247 2248 break; 2249 2250 case DELETE : 2251 case UPDATE_DELETE : 2252 ( ( Index ) index ).drop( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId ); 2253 2254 /* 2255 * If there is no value for id in this index due to our 2256 * drop above we remove the oldRdnAttr from the presence idx 2257 */ 2258 if ( null == index.reverseLookup( partitionTxn, entryId ) ) 2259 { 2260 presenceIdx.drop( partitionTxn, attributeType.getOid(), entryId ); 2261 } 2262 2263 break; 2264 2265 default : 2266 break; 2267 } 2268 } 2269 } 2270 } 2271 2272 2273 //--------------------------------------------------------------------------------------------- 2274 // The Rename operation 2275 //--------------------------------------------------------------------------------------------- 2276 /** 2277 * {@inheritDoc} 2278 */ 2279 @Override 2280 public void rename( RenameOperationContext renameContext ) throws LdapException 2281 { 2282 PartitionTxn partitionTxn = renameContext.getTransaction(); 2283 2284 assert ( partitionTxn != null ); 2285 assert ( partitionTxn instanceof PartitionWriteTxn ); 2286 2287 try 2288 { 2289 setRWLock( renameContext ); 2290 Dn oldDn = renameContext.getDn(); 2291 Rdn newRdn = renameContext.getNewRdn(); 2292 boolean deleteOldRdn = renameContext.getDeleteOldRdn(); 2293 2294 if ( renameContext.getEntry() != null ) 2295 { 2296 Entry modifiedEntry = renameContext.getModifiedEntry(); 2297 rename( partitionTxn, oldDn, newRdn, deleteOldRdn, modifiedEntry ); 2298 } 2299 else 2300 { 2301 rename( partitionTxn, oldDn, newRdn, deleteOldRdn, null ); 2302 } 2303 2304 updateCache( renameContext ); 2305 } 2306 catch ( Exception e ) 2307 { 2308 throw new LdapOperationErrorException( e.getMessage(), e ); 2309 } 2310 } 2311 2312 2313 /** 2314 * This will rename the entry, and deal with the deleteOldRdn flag. If set to true, we have 2315 * to remove the AVA which are not part of the new RDN from the entry. 2316 * If this flag is set to false, we have to take care of the special case of an AVA 2317 * which attributeType is SINGLE-VALUE : in this case, we remove the old value. 2318 */ 2319 private void rename( PartitionTxn partitionTxn, String oldId, Rdn newRdn, boolean deleteOldRdn, Entry entry ) 2320 throws LdapException, IndexNotFoundException 2321 { 2322 if ( entry == null ) 2323 { 2324 entry = master.get( partitionTxn, oldId ); 2325 } 2326 2327 Dn updn = entry.getDn(); 2328 2329 if ( !newRdn.isSchemaAware() ) 2330 { 2331 newRdn = new Rdn( schemaManager, newRdn ); 2332 } 2333 2334 /* 2335 * H A N D L E N E W R D N 2336 * ==================================================================== 2337 * Add the new Rdn attribute to the entry. If an index exists on the 2338 * new Rdn attribute we add the index for this attribute value pair. 2339 * Also we make sure that the presence index shows the existence of the 2340 * new Rdn attribute within this entry. 2341 * Last, not least, if the AttributeType is single value, take care 2342 * of removing the old value. 2343 */ 2344 for ( Ava newAtav : newRdn ) 2345 { 2346 String newNormType = newAtav.getNormType(); 2347 Object newNormValue = newAtav.getValue().getValue(); 2348 boolean oldRemoved = false; 2349 2350 AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType ); 2351 2352 if ( newRdnAttrType.isSingleValued() && entry.containsAttribute( newRdnAttrType ) ) 2353 { 2354 Attribute oldAttribute = entry.get( newRdnAttrType ); 2355 AttributeType oldAttributeType = oldAttribute.getAttributeType(); 2356 2357 // We have to remove the old attribute value, if we have some 2358 entry.removeAttributes( newRdnAttrType ); 2359 2360 // Deal with the index 2361 if ( hasUserIndexOn( newRdnAttrType ) ) 2362 { 2363 Index<?, String> userIndex = getUserIndex( newRdnAttrType ); 2364 2365 String normalized = oldAttributeType.getEquality().getNormalizer().normalize( oldAttribute.get().getValue() ); 2366 ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); 2367 2368 /* 2369 * If there is no value for id in this index due to our 2370 * drop above we remove the oldRdnAttr from the presence idx 2371 */ 2372 if ( null == userIndex.reverseLookup( partitionTxn, oldId ) ) 2373 { 2374 presenceIdx.drop( partitionTxn, newRdnAttrType.getOid(), oldId ); 2375 } 2376 2377 } 2378 } 2379 2380 if ( newRdnAttrType.getSyntax().isHumanReadable() ) 2381 { 2382 entry.add( newRdnAttrType, newAtav.getValue().getValue() ); 2383 } 2384 else 2385 { 2386 entry.add( newRdnAttrType, newAtav.getValue().getBytes() ); 2387 } 2388 2389 if ( hasUserIndexOn( newRdnAttrType ) ) 2390 { 2391 Index<?, String> userIndex = getUserIndex( newRdnAttrType ); 2392 2393 /* 2394 if ( oldRemoved ) 2395 { 2396 String normalized = newRdnAttrType.getEquality().getNormalizer().normalize( newNormValue ); 2397 ( ( Index ) userIndex ).add( normalized, id ); 2398 ( ( Index ) index ).drop( newNormValue, oldId ); 2399 } 2400 */ 2401 2402 String normalized = newRdnAttrType.getEquality().getNormalizer().normalize( ( String ) newNormValue ); 2403 ( ( Index ) userIndex ).add( partitionTxn, normalized, oldId ); 2404 2405 2406 //( ( Index ) index ).add( newNormValue, oldId ); 2407 2408 // Make sure the altered entry shows the existence of the new attrib 2409 String normTypeOid = presenceNormalizer.normalize( newNormType ); 2410 2411 if ( !presenceIdx.forward( partitionTxn, normTypeOid, oldId ) ) 2412 { 2413 presenceIdx.add( partitionTxn, normTypeOid, oldId ); 2414 } 2415 } 2416 } 2417 2418 /* 2419 * H A N D L E O L D R D N 2420 * ==================================================================== 2421 * If the old Rdn is to be removed we need to get the attribute and 2422 * value for it. Keep in mind the old Rdn need not be based on the 2423 * same attr as the new one. We remove the Rdn value from the entry 2424 * and remove the value/id tuple from the index on the old Rdn attr 2425 * if any. We also test if the delete of the old Rdn index tuple 2426 * removed all the attribute values of the old Rdn using a reverse 2427 * lookup. If so that means we blew away the last value of the old 2428 * Rdn attribute. In this case we need to remove the attrName/id 2429 * tuple from the presence index. 2430 * 2431 * We only remove an ATAV of the old Rdn if it is not included in the 2432 * new Rdn. 2433 */ 2434 2435 if ( deleteOldRdn ) 2436 { 2437 Rdn oldRdn = updn.getRdn(); 2438 2439 for ( Ava oldAtav : oldRdn ) 2440 { 2441 // check if the new ATAV is part of the old Rdn 2442 // if that is the case we do not remove the ATAV 2443 boolean mustRemove = true; 2444 2445 for ( Ava newAtav : newRdn ) 2446 { 2447 if ( oldAtav.equals( newAtav ) ) 2448 { 2449 mustRemove = false; 2450 break; 2451 } 2452 } 2453 2454 if ( mustRemove ) 2455 { 2456 String oldNormType = oldAtav.getNormType(); 2457 String oldNormValue = oldAtav.getValue().getValue(); 2458 AttributeType oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType ); 2459 entry.remove( oldRdnAttrType, oldNormValue ); 2460 2461 if ( hasUserIndexOn( oldRdnAttrType ) ) 2462 { 2463 Index<?, String> userIndex = getUserIndex( oldRdnAttrType ); 2464 2465 String normalized = oldRdnAttrType.getEquality().getNormalizer().normalize( oldNormValue ); 2466 ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); 2467 2468 /* 2469 * If there is no value for id in this index due to our 2470 * drop above we remove the oldRdnAttr from the presence idx 2471 */ 2472 if ( null == userIndex.reverseLookup( partitionTxn, oldId ) ) 2473 { 2474 String oldNormTypeOid = presenceNormalizer.normalize( oldNormType ); 2475 presenceIdx.drop( partitionTxn, oldNormTypeOid, oldId ); 2476 } 2477 } 2478 } 2479 } 2480 } 2481 2482 // Remove the EntryDN 2483 entry.removeAttributes( entryDnAT ); 2484 2485 setContextCsn( entry.get( entryCsnAT ).getString() ); 2486 2487 // And save the modified entry 2488 master.put( partitionTxn, oldId, entry ); 2489 } 2490 2491 2492 /** 2493 * {@inheritDoc} 2494 */ 2495 @SuppressWarnings("unchecked") 2496 @Override 2497 public final synchronized void rename( PartitionTxn partitionTxn, Dn dn, Rdn newRdn, boolean deleteOldRdn, Entry entry ) 2498 throws LdapException 2499 { 2500 String oldId = getEntryId( partitionTxn, dn ); 2501 2502 try 2503 { 2504 rename( partitionTxn, oldId, newRdn, deleteOldRdn, entry ); 2505 } 2506 catch ( IndexNotFoundException infe ) 2507 { 2508 throw new LdapOtherException( infe.getMessage(), infe ); 2509 } 2510 2511 /* 2512 * H A N D L E D N C H A N G E 2513 * ==================================================================== 2514 * We only need to update the Rdn index. 2515 * No need to calculate the new Dn. 2516 */ 2517 String parentId = getParentId( partitionTxn, oldId ); 2518 2519 // Get the old parentIdAndRdn to get the nb of children and descendant 2520 ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, oldId ); 2521 2522 // Now we can drop it 2523 rdnIdx.drop( partitionTxn, oldId ); 2524 2525 // Update the descendants 2526 parentIdAndRdn.setParentId( parentId ); 2527 parentIdAndRdn.setRdns( newRdn ); 2528 2529 rdnIdx.add( partitionTxn, parentIdAndRdn, oldId ); 2530 2531 entryDnCache.removeAll(); 2532 2533 if ( isSyncOnWrite.get() ) 2534 { 2535 sync(); 2536 } 2537 } 2538 2539 2540 //--------------------------------------------------------------------------------------------- 2541 // The Unbind operation 2542 //--------------------------------------------------------------------------------------------- 2543 /** 2544 * {@inheritDoc} 2545 */ 2546 @Override 2547 public final void unbind( UnbindOperationContext unbindContext ) throws LdapException 2548 { 2549 // does nothing 2550 } 2551 2552 2553 /** 2554 * This method calls {@link Partition#lookup(LookupOperationContext)} and return <tt>true</tt> 2555 * if it returns an entry by default. Please override this method if 2556 * there is more effective way for your implementation. 2557 */ 2558 @Override 2559 public boolean hasEntry( HasEntryOperationContext entryContext ) throws LdapException 2560 { 2561 PartitionTxn partitionTxn = entryContext.getTransaction(); 2562 2563 assert ( partitionTxn != null ); 2564 2565 try 2566 { 2567 setRWLock( entryContext ); 2568 2569 String id = getEntryId( partitionTxn, entryContext.getDn() ); 2570 2571 Entry entry = fetch( partitionTxn, id, entryContext.getDn() ); 2572 2573 return entry != null; 2574 } 2575 catch ( LdapException e ) 2576 { 2577 return false; 2578 } 2579 } 2580 2581 2582 //--------------------------------------------------------------------------------------------- 2583 // Helper methods 2584 //--------------------------------------------------------------------------------------------- 2585 /** 2586 * updates the CSN index 2587 * 2588 * @param partitionTxn The transaction to use 2589 * @param entry the entry having entryCSN attribute 2590 * @param id UUID of the entry 2591 * @throws Exception 2592 */ 2593 private void updateCsnIndex( PartitionTxn partitionTxn, Entry entry, String id ) throws LdapException 2594 { 2595 String entryCsn = entry.get( SchemaConstants.ENTRY_CSN_AT ).getString(); 2596 entryCsnIdx.drop( partitionTxn, id ); 2597 entryCsnIdx.add( partitionTxn, entryCsn, id ); 2598 } 2599 2600 2601 // ------------------------------------------------------------------------ 2602 // Index and master table Operations 2603 // ------------------------------------------------------------------------ 2604 /** 2605 * builds the Dn of the entry identified by the given id 2606 * 2607 * @param partitionTxn The transaction to use 2608 * @param id the entry's id 2609 * @return the normalized Dn of the entry 2610 * @throws LdapException If we can't build the entry Dn 2611 */ 2612 protected Dn buildEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException 2613 { 2614 String parentId = id; 2615 String rootId = Partition.ROOT_ID; 2616 2617 // Create an array of 10 rdns, just in case. We will extend it if needed 2618 Rdn[] rdnArray = new Rdn[10]; 2619 int pos = 0; 2620 2621 Dn dn = null; 2622 2623 try 2624 { 2625 rwLock.readLock().lock(); 2626 2627 if ( entryDnCache != null ) 2628 { 2629 Element el = entryDnCache.get( id ); 2630 2631 if ( el != null ) 2632 { 2633 return ( Dn ) el.getObjectValue(); 2634 } 2635 } 2636 2637 do 2638 { 2639 ParentIdAndRdn cur; 2640 2641 if ( piarCache != null ) 2642 { 2643 Element piar = piarCache.get( parentId ); 2644 2645 if ( piar != null ) 2646 { 2647 cur = ( ParentIdAndRdn ) piar.getObjectValue(); 2648 } 2649 else 2650 { 2651 cur = rdnIdx.reverseLookup( partitionTxn, parentId ); 2652 2653 if ( cur == null ) 2654 { 2655 return null; 2656 } 2657 2658 piarCache.put( new Element( parentId, cur ) ); 2659 } 2660 } 2661 else 2662 { 2663 cur = rdnIdx.reverseLookup( partitionTxn, parentId ); 2664 2665 if ( cur == null ) 2666 { 2667 return null; 2668 } 2669 } 2670 2671 Rdn[] rdns = cur.getRdns(); 2672 2673 for ( Rdn rdn : rdns ) 2674 { 2675 if ( ( pos > 0 ) && ( pos % 10 == 0 ) ) 2676 { 2677 // extend the array 2678 Rdn[] newRdnArray = new Rdn[pos + 10]; 2679 System.arraycopy( rdnArray, 0, newRdnArray, 0, pos ); 2680 rdnArray = newRdnArray; 2681 } 2682 2683 rdnArray[pos++] = rdn; 2684 } 2685 2686 parentId = cur.getParentId(); 2687 } 2688 while ( !parentId.equals( rootId ) ); 2689 2690 dn = new Dn( schemaManager, Arrays.copyOf( rdnArray, pos ) ); 2691 2692 entryDnCache.put( new Element( id, dn ) ); 2693 return dn; 2694 } 2695 finally 2696 { 2697 rwLock.readLock().unlock(); 2698 } 2699 } 2700 2701 2702 /** 2703 * {@inheritDoc} 2704 */ 2705 @Override 2706 public long count( PartitionTxn partitionTxn ) throws LdapException 2707 { 2708 return master.count( partitionTxn ); 2709 } 2710 2711 2712 /** 2713 * {@inheritDoc} 2714 */ 2715 @Override 2716 public final long getChildCount( PartitionTxn partitionTxn, String id ) throws LdapException 2717 { 2718 try 2719 { 2720 ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, id ); 2721 2722 return parentIdAndRdn.getNbChildren(); 2723 } 2724 catch ( Exception e ) 2725 { 2726 throw new LdapOperationErrorException( e.getMessage(), e ); 2727 } 2728 } 2729 2730 2731 /** 2732 * {@inheritDoc} 2733 */ 2734 @Override 2735 public final Dn getEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException 2736 { 2737 return buildEntryDn( partitionTxn, id ); 2738 } 2739 2740 2741 /** 2742 * {@inheritDoc} 2743 */ 2744 @Override 2745 public final String getEntryId( PartitionTxn partitionTxn, Dn dn ) throws LdapException 2746 { 2747 try 2748 { 2749 if ( Dn.isNullOrEmpty( dn ) ) 2750 { 2751 return Partition.ROOT_ID; 2752 } 2753 2754 ParentIdAndRdn suffixKey = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() ); 2755 2756 // Check into the Rdn index, starting with the partition Suffix 2757 try 2758 { 2759 rwLock.readLock().lock(); 2760 String currentId = rdnIdx.forwardLookup( partitionTxn, suffixKey ); 2761 2762 for ( int i = dn.size() - suffixDn.size(); i > 0; i-- ) 2763 { 2764 Rdn rdn = dn.getRdn( i - 1 ); 2765 ParentIdAndRdn currentRdn = new ParentIdAndRdn( currentId, rdn ); 2766 2767 currentId = rdnIdx.forwardLookup( partitionTxn, currentRdn ); 2768 2769 if ( currentId == null ) 2770 { 2771 break; 2772 } 2773 } 2774 2775 return currentId; 2776 } 2777 finally 2778 { 2779 rwLock.readLock().unlock(); 2780 } 2781 } 2782 catch ( Exception e ) 2783 { 2784 throw new LdapException( e.getMessage(), e ); 2785 } 2786 } 2787 2788 2789 /** 2790 * {@inheritDoc} 2791 */ 2792 @Override 2793 public String getParentId( PartitionTxn partitionTxn, String childId ) throws LdapException 2794 { 2795 try 2796 { 2797 rwLock.readLock().lock(); 2798 ParentIdAndRdn key = rdnIdx.reverseLookup( partitionTxn, childId ); 2799 2800 if ( key == null ) 2801 { 2802 return null; 2803 } 2804 2805 return key.getParentId(); 2806 } 2807 finally 2808 { 2809 rwLock.readLock().unlock(); 2810 } 2811 } 2812 2813 2814 /** 2815 * Retrieve the SuffixID 2816 * 2817 * @param partitionTxn The transaction to use 2818 * @return The Suffix ID 2819 * @throws LdapException If we weren't able to retrieve the Suffix ID 2820 */ 2821 public String getSuffixId( PartitionTxn partitionTxn ) throws LdapException 2822 { 2823 if ( suffixId == null ) 2824 { 2825 ParentIdAndRdn key = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() ); 2826 2827 try 2828 { 2829 rwLock.readLock().lock(); 2830 suffixId = rdnIdx.forwardLookup( partitionTxn, key ); 2831 } 2832 finally 2833 { 2834 rwLock.readLock().unlock(); 2835 } 2836 } 2837 2838 return suffixId; 2839 } 2840 2841 2842 //------------------------------------------------------------------------ 2843 // Index handling 2844 //------------------------------------------------------------------------ 2845 /** 2846 * {@inheritDoc} 2847 */ 2848 @Override 2849 public void addIndex( Index<?, String> index ) throws LdapException 2850 { 2851 checkInitialized( "addIndex" ); 2852 2853 // Check that the index String is valid 2854 AttributeType attributeType = null; 2855 2856 try 2857 { 2858 attributeType = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ); 2859 } 2860 catch ( LdapNoSuchAttributeException lnsae ) 2861 { 2862 LOG.error( "Cannot initialize the index for AttributeType {}, this value does not exist", 2863 index.getAttributeId() ); 2864 2865 return; 2866 } 2867 2868 String oid = attributeType.getOid(); 2869 2870 if ( SYS_INDEX_OIDS.contains( oid ) ) 2871 { 2872 if ( !systemIndices.containsKey( oid ) ) 2873 { 2874 systemIndices.put( oid, index ); 2875 } 2876 } 2877 else 2878 { 2879 if ( !userIndices.containsKey( oid ) ) 2880 { 2881 userIndices.put( oid, index ); 2882 } 2883 } 2884 } 2885 2886 2887 /** 2888 * Add some new indexes 2889 * @param indexes The added indexes 2890 */ 2891 public void addIndexedAttributes( Index<?, String>... indexes ) 2892 { 2893 for ( Index<?, String> index : indexes ) 2894 { 2895 indexedAttributes.add( index ); 2896 } 2897 } 2898 2899 2900 /** 2901 * Set the list of indexes for this partition 2902 * @param indexedAttributes The list of indexes 2903 */ 2904 public void setIndexedAttributes( Set<Index<?, String>> indexedAttributes ) 2905 { 2906 this.indexedAttributes = indexedAttributes; 2907 } 2908 2909 2910 /** 2911 * @return The list of indexed attributes 2912 */ 2913 public Set<Index<?, String>> getIndexedAttributes() 2914 { 2915 return indexedAttributes; 2916 } 2917 2918 2919 /** 2920 * {@inheritDoc} 2921 */ 2922 @Override 2923 public Iterator<String> getUserIndices() 2924 { 2925 return userIndices.keySet().iterator(); 2926 } 2927 2928 2929 /** 2930 * {@inheritDoc} 2931 */ 2932 @Override 2933 public Iterator<String> getSystemIndices() 2934 { 2935 return systemIndices.keySet().iterator(); 2936 } 2937 2938 2939 /** 2940 * {@inheritDoc} 2941 */ 2942 @Override 2943 public Index<?, String> getIndex( AttributeType attributeType ) throws IndexNotFoundException 2944 { 2945 String id = attributeType.getOid(); 2946 2947 if ( userIndices.containsKey( id ) ) 2948 { 2949 return userIndices.get( id ); 2950 } 2951 2952 if ( systemIndices.containsKey( id ) ) 2953 { 2954 return systemIndices.get( id ); 2955 } 2956 2957 throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, id ) ); 2958 } 2959 2960 2961 /** 2962 * {@inheritDoc} 2963 */ 2964 @Override 2965 public Index<?, String> getUserIndex( AttributeType attributeType ) throws IndexNotFoundException 2966 { 2967 if ( attributeType == null ) 2968 { 2969 throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) ); 2970 } 2971 2972 String oid = attributeType.getOid(); 2973 2974 if ( userIndices.containsKey( oid ) ) 2975 { 2976 return userIndices.get( oid ); 2977 } 2978 2979 throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) ); 2980 } 2981 2982 2983 /** 2984 * {@inheritDoc} 2985 */ 2986 @Override 2987 public Index<?, String> getSystemIndex( AttributeType attributeType ) throws IndexNotFoundException 2988 { 2989 if ( attributeType == null ) 2990 { 2991 throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) ); 2992 } 2993 2994 String oid = attributeType.getOid(); 2995 2996 if ( systemIndices.containsKey( oid ) ) 2997 { 2998 return systemIndices.get( oid ); 2999 } 3000 3001 throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) ); 3002 } 3003 3004 3005 /** 3006 * {@inheritDoc} 3007 */ 3008 @SuppressWarnings("unchecked") 3009 @Override 3010 public Index<Dn, String> getAliasIndex() 3011 { 3012 return ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 3013 } 3014 3015 3016 /** 3017 * {@inheritDoc} 3018 */ 3019 @SuppressWarnings("unchecked") 3020 @Override 3021 public Index<String, String> getOneAliasIndex() 3022 { 3023 return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID ); 3024 } 3025 3026 3027 /** 3028 * {@inheritDoc} 3029 */ 3030 @SuppressWarnings("unchecked") 3031 @Override 3032 public Index<String, String> getSubAliasIndex() 3033 { 3034 return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID ); 3035 } 3036 3037 3038 /** 3039 * {@inheritDoc} 3040 */ 3041 @SuppressWarnings("unchecked") 3042 @Override 3043 public Index<String, String> getObjectClassIndex() 3044 { 3045 return ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID ); 3046 } 3047 3048 3049 /** 3050 * {@inheritDoc} 3051 */ 3052 @SuppressWarnings("unchecked") 3053 @Override 3054 public Index<String, String> getEntryCsnIndex() 3055 { 3056 return ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID ); 3057 } 3058 3059 3060 /** 3061 * {@inheritDoc} 3062 */ 3063 @SuppressWarnings("unchecked") 3064 public Index<String, String> getAdministrativeRoleIndex() 3065 { 3066 return ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ); 3067 } 3068 3069 3070 /** 3071 * {@inheritDoc} 3072 */ 3073 @SuppressWarnings("unchecked") 3074 @Override 3075 public Index<String, String> getPresenceIndex() 3076 { 3077 return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID ); 3078 } 3079 3080 3081 /** 3082 * {@inheritDoc} 3083 */ 3084 @SuppressWarnings("unchecked") 3085 @Override 3086 public Index<ParentIdAndRdn, String> getRdnIndex() 3087 { 3088 return ( Index<ParentIdAndRdn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_RDN_AT_OID ); 3089 } 3090 3091 3092 /** 3093 * {@inheritDoc} 3094 */ 3095 @Override 3096 public boolean hasUserIndexOn( AttributeType attributeType ) throws LdapException 3097 { 3098 String oid = attributeType.getOid(); 3099 return userIndices.containsKey( oid ); 3100 } 3101 3102 3103 /** 3104 * {@inheritDoc} 3105 */ 3106 @Override 3107 public boolean hasSystemIndexOn( AttributeType attributeType ) throws LdapException 3108 { 3109 return systemIndices.containsKey( attributeType.getOid() ); 3110 } 3111 3112 3113 /** 3114 * {@inheritDoc} 3115 */ 3116 @Override 3117 public boolean hasIndexOn( AttributeType attributeType ) throws LdapException 3118 { 3119 return hasUserIndexOn( attributeType ) || hasSystemIndexOn( attributeType ); 3120 } 3121 3122 3123 //--------------------------------------------------------------------------------------------- 3124 // Alias index manipulation 3125 //--------------------------------------------------------------------------------------------- 3126 /** 3127 * Adds userIndices for an aliasEntry to be added to the database while checking 3128 * for constrained alias constructs like alias cycles and chaining. 3129 * 3130 * @param partitionTxn The transaction to use 3131 * @param aliasDn normalized distinguished name for the alias entry 3132 * @param aliasTarget the user provided aliased entry dn as a string 3133 * @param aliasId the id of alias entry to add 3134 * @throws LdapException if index addition fails, and if the alias is 3135 * not allowed due to chaining or cycle formation. 3136 * @throws LdapException if the wrappedCursor btrees cannot be altered 3137 */ 3138 protected void addAliasIndices( PartitionTxn partitionTxn, String aliasId, Dn aliasDn, Dn aliasTarget ) 3139 throws LdapException 3140 { 3141 String targetId; // Id of the aliasedObjectName 3142 Dn ancestorDn; // Name of an alias entry relative 3143 String ancestorId; // Id of an alias entry relative 3144 3145 /* 3146 * Check For Aliases External To Naming Context 3147 * 3148 * id may be null but the alias may be to a valid entry in 3149 * another namingContext. Such aliases are not allowed and we 3150 * need to point it out to the user instead of saying the target 3151 * does not exist when it potentially could outside of this upSuffix. 3152 */ 3153 if ( !aliasTarget.isDescendantOf( suffixDn ) ) 3154 { 3155 String msg = I18n.err( I18n.ERR_225, suffixDn.getName() ); 3156 throw new LdapAliasDereferencingException( msg ); 3157 } 3158 3159 // L O O K U P T A R G E T I D 3160 targetId = getEntryId( partitionTxn, aliasTarget ); 3161 3162 /* 3163 * Check For Target Existence 3164 * 3165 * We do not allow the creation of inconsistent aliases. Aliases should 3166 * not be broken links. If the target does not exist we start screaming 3167 */ 3168 if ( null == targetId ) 3169 { 3170 // Complain about target not existing 3171 String msg = I18n.err( I18n.ERR_581, aliasDn.getName(), aliasTarget ); 3172 throw new LdapAliasException( msg ); 3173 } 3174 3175 /* 3176 * Detect Direct Alias Chain Creation 3177 * 3178 * Rather than resusitate the target to test if it is an alias and fail 3179 * due to chaing creation we use the alias index to determine if the 3180 * target is an alias. Hence if the alias we are about to create points 3181 * to another alias as its target in the aliasedObjectName attribute, 3182 * then we have a situation where an alias chain is being created. 3183 * Alias chaining is not allowed so we throw and exception. 3184 */ 3185 if ( null != aliasIdx.reverseLookup( partitionTxn, targetId ) ) 3186 { 3187 String msg = I18n.err( I18n.ERR_227 ); 3188 throw new LdapAliasDereferencingException( msg ); 3189 } 3190 3191 // Add the alias to the simple alias index 3192 aliasIdx.add( partitionTxn, aliasTarget, aliasId ); 3193 3194 if ( aliasCache != null ) 3195 { 3196 aliasCache.put( new Element( aliasId, aliasTarget ) ); 3197 } 3198 3199 /* 3200 * Handle One Level Scope Alias Index 3201 * 3202 * The first relative is special with respect to the one level alias 3203 * index. If the target is not a sibling of the alias then we add the 3204 * index entry maping the parent's id to the aliased target id. 3205 */ 3206 ancestorDn = aliasDn.getParent(); 3207 ancestorId = getEntryId( partitionTxn, ancestorDn ); 3208 3209 // check if alias parent and aliased entry are the same 3210 Dn normalizedAliasTargetParentDn = aliasTarget.getParent(); 3211 3212 if ( !aliasDn.isDescendantOf( normalizedAliasTargetParentDn ) ) 3213 { 3214 oneAliasIdx.add( partitionTxn, ancestorId, targetId ); 3215 } 3216 3217 /* 3218 * Handle Sub Level Scope Alias Index 3219 * 3220 * Walk the list of relatives from the parents up to the upSuffix, testing 3221 * to see if the alias' target is a descendant of the relative. If the 3222 * alias target is not a descentant of the relative it extends the scope 3223 * and is added to the sub tree scope alias index. The upSuffix node is 3224 * ignored since everything is under its scope. The first loop 3225 * iteration shall handle the parents. 3226 */ 3227 while ( !ancestorDn.equals( suffixDn ) && null != ancestorId ) 3228 { 3229 if ( !aliasTarget.isDescendantOf( ancestorDn ) ) 3230 { 3231 subAliasIdx.add( partitionTxn, ancestorId, targetId ); 3232 } 3233 3234 ancestorDn = ancestorDn.getParent(); 3235 ancestorId = getEntryId( partitionTxn, ancestorDn ); 3236 } 3237 } 3238 3239 3240 /** 3241 * Removes the index entries for an alias before the entry is deleted from 3242 * the master table. 3243 * 3244 * TODO Optimize this by walking the hierarchy index instead of the name 3245 * 3246 * @param partitionTxn The transaction to use 3247 * @param aliasId the id of the alias entry in the master table 3248 * @throws LdapException if we cannot delete index values in the database 3249 */ 3250 protected void dropAliasIndices( PartitionTxn partitionTxn, String aliasId ) throws LdapException 3251 { 3252 Dn targetDn = aliasIdx.reverseLookup( partitionTxn, aliasId ); 3253 3254 if ( !targetDn.isSchemaAware() ) 3255 { 3256 targetDn = new Dn( schemaManager, targetDn ); 3257 } 3258 3259 String targetId = getEntryId( partitionTxn, targetDn ); 3260 3261 if ( targetId == null ) 3262 { 3263 // the entry doesn't exist, probably it has been deleted or renamed 3264 // TODO: this is just a workaround for now, the alias indices should be updated when target entry is deleted or removed 3265 return; 3266 } 3267 3268 Dn aliasDn = getEntryDn( partitionTxn, aliasId ); 3269 3270 Dn ancestorDn = aliasDn.getParent(); 3271 String ancestorId = getEntryId( partitionTxn, ancestorDn ); 3272 3273 /* 3274 * We cannot just drop all tuples in the one level and subtree userIndices 3275 * linking baseIds to the targetId. If more than one alias refers to 3276 * the target then droping all tuples with a value of targetId would 3277 * make all other aliases to the target inconsistent. 3278 * 3279 * We need to walk up the path of alias ancestors until we reach the 3280 * upSuffix, deleting each ( ancestorId, targetId ) tuple in the 3281 * subtree scope alias. We only need to do this for the direct parent 3282 * of the alias on the one level subtree. 3283 */ 3284 oneAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3285 subAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3286 3287 while ( !ancestorDn.equals( suffixDn ) && ancestorDn.size() > suffixDn.size() ) 3288 { 3289 ancestorDn = ancestorDn.getParent(); 3290 ancestorId = getEntryId( partitionTxn, ancestorDn ); 3291 3292 subAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3293 } 3294 3295 // Drops all alias tuples pointing to the id of the alias to be deleted 3296 aliasIdx.drop( partitionTxn, aliasId ); 3297 3298 if ( aliasCache != null ) 3299 { 3300 aliasCache.remove( aliasId ); 3301 } 3302 } 3303 3304 3305 /** 3306 * For all aliases including and under the moved base, this method removes 3307 * one and subtree alias index tuples for old ancestors above the moved base 3308 * that will no longer be ancestors after the move. 3309 * 3310 * @param partitionTxn The transaction to use 3311 * @param movedBase the base at which the move occurred - the moved node 3312 * @throws LdapException if system userIndices fail 3313 */ 3314 protected void dropMovedAliasIndices( PartitionTxn partitionTxn, Dn movedBase ) throws LdapException 3315 { 3316 String movedBaseId = getEntryId( partitionTxn, movedBase ); 3317 3318 Dn targetDn = aliasIdx.reverseLookup( partitionTxn, movedBaseId ); 3319 3320 if ( targetDn != null ) 3321 { 3322 if ( !targetDn.isSchemaAware() ) 3323 { 3324 targetDn = new Dn( schemaManager, targetDn ); 3325 } 3326 3327 String targetId = getEntryId( partitionTxn, targetDn ); 3328 Dn aliasDn = getEntryDn( partitionTxn, movedBaseId ); 3329 3330 /* 3331 * Start droping index tuples with the first ancestor right above the 3332 * moved base. This is the first ancestor effected by the move. 3333 */ 3334 Dn ancestorDn = movedBase.getParent(); 3335 String ancestorId = getEntryId( partitionTxn, ancestorDn ); 3336 3337 /* 3338 * We cannot just drop all tuples in the one level and subtree userIndices 3339 * linking baseIds to the targetId. If more than one alias refers to 3340 * the target then droping all tuples with a value of targetId would 3341 * make all other aliases to the target inconsistent. 3342 * 3343 * We need to walk up the path of alias ancestors right above the moved 3344 * base until we reach the upSuffix, deleting each ( ancestorId, 3345 * targetId ) tuple in the subtree scope alias. We only need to do 3346 * this for the direct parent of the alias on the one level subtree if 3347 * the moved base is the alias. 3348 */ 3349 if ( aliasDn.equals( movedBase ) ) 3350 { 3351 oneAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3352 } 3353 3354 subAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3355 3356 while ( !ancestorDn.equals( suffixDn ) ) 3357 { 3358 ancestorDn = ancestorDn.getParent(); 3359 ancestorId = getEntryId( partitionTxn, ancestorDn ); 3360 3361 subAliasIdx.drop( partitionTxn, ancestorId, targetId ); 3362 } 3363 } 3364 } 3365 3366 3367 //--------------------------------------------------------------------------------------------- 3368 // Debug methods 3369 //--------------------------------------------------------------------------------------------- 3370 private void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, Index<?, String> index ) 3371 { 3372 try 3373 { 3374 Cursor<IndexEntry<?, String>> cursor = ( Cursor ) index.forwardCursor( partitionTxn ); 3375 3376 while ( cursor.next() ) 3377 { 3378 IndexEntry<?, String> entry = cursor.get(); 3379 3380 System.out.println( entry ); 3381 } 3382 } 3383 catch ( Exception e ) 3384 { 3385 // TODO : fixme 3386 } 3387 } 3388 3389 3390 /** 3391 * {@inheritDoc} 3392 */ 3393 @Override 3394 public void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, String name ) throws IOException 3395 { 3396 try 3397 { 3398 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( name ); 3399 3400 if ( attributeType == null ) 3401 { 3402 stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) ); 3403 3404 return; 3405 } 3406 3407 if ( attributeType.getOid().equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) ) 3408 { 3409 dumpIndex( partitionTxn, stream, rdnIdx ); 3410 } 3411 } 3412 catch ( LdapException le ) 3413 { 3414 stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) ); 3415 } 3416 } 3417 3418 3419 /** 3420 * {@inheritDoc} 3421 */ 3422 @Override 3423 public String toString() 3424 { 3425 return "Partition<" + id + ">"; 3426 } 3427 3428 3429 /** 3430 * Create a new Index for a given OID 3431 * 3432 * @param indexOid The Attribute OID 3433 * @param path The working directory where this index will be stored 3434 * @param withReverse If the Reverse index must be created or not 3435 * @return The created index 3436 * @throws LdapException If the index can't be created 3437 */ 3438 protected abstract Index createSystemIndex( String indexOid, URI path, boolean withReverse ) throws LdapException; 3439 3440 3441 /** 3442 * {@inheritDoc} 3443 */ 3444 @Override 3445 public MasterTable getMasterTable() 3446 { 3447 return master; 3448 } 3449 3450 3451 /** 3452 * Acquire a Read lock 3453 */ 3454 private void lockRead() 3455 { 3456 rwLock.readLock().lock(); 3457 } 3458 3459 3460 /** 3461 * Release a Read lock 3462 */ 3463 private void unlockRead() 3464 { 3465 rwLock.readLock().unlock(); 3466 } 3467 3468 3469 /** 3470 * Acquire a Write lock 3471 */ 3472 private void lockWrite() 3473 { 3474 rwLock.writeLock().lock(); 3475 } 3476 3477 3478 /** 3479 * Release a Write lock 3480 */ 3481 private void unlockWrite() 3482 { 3483 rwLock.writeLock().unlock(); 3484 } 3485 3486 3487 /** 3488 * updates the cache based on the type of OperationContext 3489 * 3490 * @param opCtx the operation's context 3491 */ 3492 public void updateCache( OperationContext opCtx ) 3493 { 3494 // partition implementations should override this if they want to use cache 3495 } 3496 3497 3498 /** 3499 * looks up for the entry with the given ID in the cache 3500 * 3501 * @param id the ID of the entry 3502 * @return the Entry if exists, null otherwise 3503 */ 3504 public Entry lookupCache( String id ) 3505 { 3506 return null; 3507 } 3508 3509 3510 /** 3511 * adds the given entry to cache 3512 * 3513 * Note: this method is not called during add operation to avoid filling the cache 3514 * with all the added entries 3515 * 3516 * @param id ID of the entry 3517 * @param entry the Entry 3518 */ 3519 public void addToCache( String id, Entry entry ) 3520 { 3521 } 3522 3523 3524 /** 3525 * @return the optimizer 3526 */ 3527 public Optimizer getOptimizer() 3528 { 3529 return optimizer; 3530 } 3531 3532 3533 /** 3534 * @param optimizer the optimizer to set 3535 */ 3536 public void setOptimizer( Optimizer optimizer ) 3537 { 3538 this.optimizer = optimizer; 3539 } 3540 3541 3542 /** 3543 * @param searchEngine the searchEngine to set 3544 */ 3545 public void setSearchEngine( SearchEngine searchEngine ) 3546 { 3547 this.searchEngine = searchEngine; 3548 } 3549 3550 3551 /** 3552 * Set and return the ReadWrite lock we use to protect the backend against concurrent modifications 3553 * 3554 * @param operationContext The OperationContext which contain the reference to the OperationManager 3555 */ 3556 private void setRWLock( OperationContext operationContext ) 3557 { 3558 if ( operationContext.getSession() != null ) 3559 { 3560 rwLock = operationContext.getSession().getDirectoryService().getOperationManager().getRWLock(); 3561 } 3562 else 3563 { 3564 if ( rwLock == null ) 3565 { 3566 // Create a ReadWrite lock from scratch 3567 rwLock = new ReentrantReadWriteLock(); 3568 } 3569 } 3570 } 3571 3572 3573 /** 3574 * {@inheritDoc} 3575 */ 3576 @Override 3577 public ReadWriteLock getReadWriteLock() 3578 { 3579 return rwLock; 3580 } 3581 3582 3583 /** 3584 * {@inheritDoc} 3585 */ 3586 @Override 3587 public Cache getAliasCache() 3588 { 3589 return aliasCache; 3590 } 3591 3592 3593 /** 3594 * {@inheritDoc} 3595 */ 3596 @Override 3597 public String getContextCsn( PartitionTxn partitionTxn ) 3598 { 3599 if ( super.getContextCsn( partitionTxn ) == null ) 3600 { 3601 loadContextCsn( partitionTxn ); 3602 } 3603 3604 return super.getContextCsn( partitionTxn ); 3605 } 3606 3607 3608 /** 3609 * Loads the current context CSN present in the context entry of the partition 3610 * 3611 * @param partitionTxn The transaction to use 3612 */ 3613 protected void loadContextCsn( PartitionTxn partitionTxn ) 3614 { 3615 try 3616 { 3617 if ( rwLock == null ) 3618 { 3619 // Create a ReadWrite lock from scratch 3620 rwLock = new ReentrantReadWriteLock(); 3621 } 3622 3623 // load the last stored valid CSN value 3624 String contextEntryId = getEntryId( partitionTxn, getSuffixDn() ); 3625 3626 if ( contextEntryId == null ) 3627 { 3628 return; 3629 } 3630 3631 Entry entry = fetch( partitionTxn, contextEntryId ); 3632 3633 Attribute ctxCsnAt = entry.get( contextCsnAT ); 3634 3635 if ( ctxCsnAt != null ) 3636 { 3637 setContextCsn( ctxCsnAt.getString() ); 3638 ctxCsnChanged = false; // this is just loaded, not new 3639 } 3640 } 3641 catch ( LdapException e ) 3642 { 3643 throw new RuntimeException( e ); 3644 } 3645 } 3646 3647 3648 /** 3649 * {@inheritDoc} 3650 */ 3651 // store the contextCSN value in the context entry 3652 // note that this modification shouldn't change the entryCSN value of the context entry 3653 @Override 3654 public void saveContextCsn( PartitionTxn partitionTxn ) throws LdapException 3655 { 3656 if ( !ctxCsnChanged ) 3657 { 3658 return; 3659 } 3660 3661 String contextCsn = super.getContextCsn( partitionTxn ); 3662 3663 if ( contextCsn == null ) 3664 { 3665 return; 3666 } 3667 3668 try 3669 { 3670 // we don't need to use the ctxCsnSemaphore here cause 3671 // the only other place this is called is from PartitionNexus.sync() 3672 // but that is protected by write lock in DefaultDirectoryService.shutdown() 3673 3674 String contextEntryId = getEntryId( partitionTxn, getSuffixDn() ); 3675 Entry origEntry = fetch( partitionTxn, contextEntryId ); 3676 3677 // The Context Entry may have been deleted. Get out if we don't find it 3678 if ( origEntry == null ) 3679 { 3680 return; 3681 } 3682 3683 origEntry = ( ( ClonedServerEntry ) origEntry ).getOriginalEntry(); 3684 3685 origEntry.removeAttributes( contextCsnAT, entryDnAT ); 3686 3687 origEntry.add( contextCsnAT, contextCsn ); 3688 3689 master.put( partitionTxn, contextEntryId, origEntry ); 3690 3691 ctxCsnChanged = false; 3692 3693 LOG.debug( "Saved context CSN {} for the partition {}", contextCsn, suffixDn ); 3694 } 3695 catch ( Exception e ) 3696 { 3697 throw new LdapOperationErrorException( e.getMessage(), e ); 3698 } 3699 } 3700 3701 3702 /** 3703 * {@inheritDoc} 3704 */ 3705 @Override 3706 public Subordinates getSubordinates( PartitionTxn partitionTxn, Entry entry ) throws LdapException 3707 { 3708 Subordinates subordinates = new Subordinates(); 3709 3710 try 3711 { 3712 // Check into the Rdn index, starting with the partition Suffix 3713 try 3714 { 3715 rwLock.readLock().lock(); 3716 ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, entry.get( SchemaConstants.ENTRY_UUID_AT ).getString() ); 3717 3718 subordinates.setNbChildren( parentIdAndRdn.getNbChildren() ); 3719 subordinates.setNbSubordinates( parentIdAndRdn.getNbDescendants() ); 3720 } 3721 finally 3722 { 3723 rwLock.readLock().unlock(); 3724 } 3725 } 3726 catch ( Exception e ) 3727 { 3728 throw new LdapException( e.getMessage(), e ); 3729 } 3730 3731 return subordinates; 3732 } 3733}