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