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.jdbm; 021 022 023import java.io.File; 024import java.io.FilenameFilter; 025import java.io.IOException; 026import java.net.URI; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.List; 030import java.util.UUID; 031 032import org.apache.directory.api.ldap.model.constants.SchemaConstants; 033import org.apache.directory.api.ldap.model.csn.CsnFactory; 034import org.apache.directory.api.ldap.model.cursor.Cursor; 035import org.apache.directory.api.ldap.model.cursor.CursorException; 036import org.apache.directory.api.ldap.model.cursor.Tuple; 037import org.apache.directory.api.ldap.model.entry.Attribute; 038import org.apache.directory.api.ldap.model.entry.DefaultEntry; 039import org.apache.directory.api.ldap.model.entry.Entry; 040import org.apache.directory.api.ldap.model.entry.Value; 041import org.apache.directory.api.ldap.model.exception.LdapException; 042import org.apache.directory.api.ldap.model.exception.LdapOtherException; 043import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; 044import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 045import org.apache.directory.api.ldap.model.name.Dn; 046import org.apache.directory.api.ldap.model.schema.AttributeType; 047import org.apache.directory.api.ldap.model.schema.SchemaManager; 048import org.apache.directory.api.util.exception.MultiException; 049import org.apache.directory.server.constants.ApacheSchemaConstants; 050import org.apache.directory.server.core.api.DnFactory; 051import org.apache.directory.server.core.api.entry.ClonedServerEntry; 052import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 053import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 054import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 055import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 056import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 057import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 058import org.apache.directory.server.core.api.interceptor.context.OperationContext; 059import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 060import org.apache.directory.server.core.api.partition.Partition; 061import org.apache.directory.server.core.api.partition.PartitionReadTxn; 062import org.apache.directory.server.core.api.partition.PartitionTxn; 063import org.apache.directory.server.core.api.partition.PartitionWriteTxn; 064import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition; 065import org.apache.directory.server.i18n.I18n; 066import org.apache.directory.server.xdbm.Index; 067import org.apache.directory.server.xdbm.ParentIdAndRdn; 068import org.apache.directory.server.xdbm.search.impl.CursorBuilder; 069import org.apache.directory.server.xdbm.search.impl.DefaultOptimizer; 070import org.apache.directory.server.xdbm.search.impl.DefaultSearchEngine; 071import org.apache.directory.server.xdbm.search.impl.EvaluatorBuilder; 072import org.apache.directory.server.xdbm.search.impl.NoOpOptimizer; 073import org.slf4j.Logger; 074import org.slf4j.LoggerFactory; 075 076import com.github.benmanes.caffeine.cache.Cache; 077import com.github.benmanes.caffeine.cache.Caffeine; 078 079import jdbm.RecordManager; 080import jdbm.helper.MRU; 081import jdbm.recman.BaseRecordManager; 082import jdbm.recman.CacheRecordManager; 083import jdbm.recman.TransactionManager; 084 085 086/** 087 * A {@link Partition} that stores entries in 088 * <a href="http://jdbm.sourceforge.net/">JDBM</a> database. 089 * 090 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 091 */ 092public class JdbmPartition extends AbstractBTreePartition 093{ 094 /** static logger */ 095 private static final Logger LOG = LoggerFactory.getLogger( JdbmPartition.class ); 096 097 private static final String JDBM_DB_FILE_EXTN = ".db"; 098 099 private static final FilenameFilter DB_FILTER = new FilenameFilter() 100 { 101 @Override 102 public boolean accept( File dir, String name ) 103 { 104 // really important to filter master.db and master.lg files 105 return name.endsWith( JDBM_DB_FILE_EXTN ) && !name.startsWith( "master." ); 106 } 107 }; 108 109 /** the JDBM record manager used by this database */ 110 private RecordManager recMan; 111 112 /** the entry cache */ 113 private Cache< String, Entry > entryCache; 114 115 116 /** 117 * Creates a store based on JDBM B+Trees. 118 * 119 * @param schemaManager The SchemaManager instance 120 * @param dnFactory The DN factory instance 121 */ 122 public JdbmPartition( SchemaManager schemaManager, DnFactory dnFactory ) 123 { 124 super( schemaManager, dnFactory ); 125 126 // Initialize the cache size 127 if ( cacheSize < 0 ) 128 { 129 cacheSize = DEFAULT_CACHE_SIZE; 130 LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id ); 131 } 132 else 133 { 134 LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id ); 135 } 136 } 137 138 139 /** 140 * Rebuild the indexes 141 */ 142 private int rebuildIndexes( PartitionTxn partitionTxn ) throws LdapException, IOException 143 { 144 Cursor<Tuple<String, Entry>> cursor = getMasterTable().cursor(); 145 146 int masterTableCount = 0; 147 int repaired = 0; 148 149 System.out.println( "Re-building indices..." ); 150 151 boolean ctxEntryLoaded = false; 152 153 try 154 { 155 while ( cursor.next() ) 156 { 157 masterTableCount++; 158 Tuple<String, Entry> tuple = cursor.get(); 159 String id = tuple.getKey(); 160 161 Entry entry = tuple.getValue(); 162 163 // Start with the RdnIndex 164 String parentId = entry.get( ApacheSchemaConstants.ENTRY_PARENT_ID_OID ).getString(); 165 System.out.println( "Read entry " + entry.getDn() + " with ID " + id + " and parent ID " + parentId ); 166 167 Dn dn = entry.getDn(); 168 169 ParentIdAndRdn parentIdAndRdn = null; 170 171 // context entry may have more than one RDN 172 if ( !ctxEntryLoaded && getSuffixDn().getName().startsWith( dn.getName() ) ) 173 { 174 // If the read entry is the context entry, inject a tuple that have one or more RDNs 175 parentIdAndRdn = new ParentIdAndRdn( parentId, getSuffixDn().getRdns() ); 176 ctxEntryLoaded = true; 177 } 178 else 179 { 180 parentIdAndRdn = new ParentIdAndRdn( parentId, dn.getRdn() ); 181 } 182 183 // Inject the parentIdAndRdn in the rdnIndex 184 rdnIdx.add( partitionTxn, parentIdAndRdn, id ); 185 186 // Process the ObjectClass index 187 // Update the ObjectClass index 188 Attribute objectClass = entry.get( objectClassAT ); 189 190 if ( objectClass == null ) 191 { 192 String msg = I18n.err( I18n.ERR_217, dn, entry ); 193 ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION; 194 throw new LdapSchemaViolationException( rc, msg ); 195 } 196 197 for ( Value value : objectClass ) 198 { 199 String valueStr = value.getString(); 200 201 if ( valueStr.equals( SchemaConstants.TOP_OC ) ) 202 { 203 continue; 204 } 205 206 objectClassIdx.add( partitionTxn, valueStr, id ); 207 } 208 209 // The Alias indexes 210 if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) 211 { 212 Attribute aliasAttr = entry.get( aliasedObjectNameAT ); 213 addAliasIndices( partitionTxn, id, dn, new Dn( schemaManager, aliasAttr.getString() ) ); 214 } 215 216 // The entryCSN index 217 // Update the EntryCsn index 218 Attribute entryCsn = entry.get( entryCsnAT ); 219 220 if ( entryCsn == null ) 221 { 222 String msg = I18n.err( I18n.ERR_219, dn, entry ); 223 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg ); 224 } 225 226 entryCsnIdx.add( partitionTxn, entryCsn.getString(), id ); 227 228 // The AdministrativeRole index 229 // Update the AdministrativeRole index, if needed 230 if ( entry.containsAttribute( administrativeRoleAT ) ) 231 { 232 // We may have more than one role 233 Attribute adminRoles = entry.get( administrativeRoleAT ); 234 235 for ( Value value : adminRoles ) 236 { 237 adminRoleIdx.add( partitionTxn, value.getString(), id ); 238 } 239 240 // Adds only those attributes that are indexed 241 presenceIdx.add( partitionTxn, administrativeRoleAT.getOid(), id ); 242 } 243 244 // And the user indexess 245 // Now work on the user defined userIndices 246 for ( Attribute attribute : entry ) 247 { 248 AttributeType attributeType = attribute.getAttributeType(); 249 String attributeOid = attributeType.getOid(); 250 251 if ( hasUserIndexOn( attributeType ) ) 252 { 253 Index<Object, String> idx = ( Index<Object, String> ) getUserIndex( attributeType ); 254 255 // here lookup by attributeId is OK since we got attributeId from 256 // the entry via the enumeration - it's in there as is for sure 257 258 for ( Value value : attribute ) 259 { 260 idx.add( partitionTxn, value.getString(), id ); 261 } 262 263 // Adds only those attributes that are indexed 264 presenceIdx.add( partitionTxn, attributeOid, id ); 265 } 266 } 267 } 268 269 } 270 catch ( Exception e ) 271 { 272 System.out.println( "Exiting after fetching entries " + repaired ); 273 throw new LdapOtherException( e.getMessage(), e ); 274 } 275 finally 276 { 277 cursor.close(); 278 } 279 280 return masterTableCount; 281 } 282 283 284 /** 285 * Update the children and descendant counters in the RDN index 286 */ 287 private void updateRdnIndexCounters( PartitionTxn partitionTxn ) throws LdapException, IOException 288 { 289 Cursor<Tuple<String, Entry>> cursor = getMasterTable().cursor(); 290 291 System.out.println( "Updating the RDN index counters..." ); 292 293 try 294 { 295 while ( cursor.next() ) 296 { 297 Tuple<String, Entry> tuple = cursor.get(); 298 299 Entry entry = tuple.getValue(); 300 301 // Update the parent's nbChildren and nbDescendants values 302 // Start with the RdnIndex 303 String parentId = entry.get( ApacheSchemaConstants.ENTRY_PARENT_ID_OID ).getString(); 304 305 if ( parentId != Partition.ROOT_ID ) 306 { 307 updateRdnIdx( partitionTxn, parentId, ADD_CHILD, 0 ); 308 } 309 } 310 } 311 catch ( Exception e ) 312 { 313 System.out.println( "Exiting, wasn't able to update the RDN index counters" ); 314 throw new LdapOtherException( e.getMessage(), e ); 315 } 316 finally 317 { 318 cursor.close(); 319 } 320 } 321 322 323 /** 324 * {@inheritDoc} 325 */ 326 @Override 327 protected void doRepair() throws LdapException 328 { 329 BaseRecordManager base; 330 331 try 332 { 333 base = new BaseRecordManager( getPartitionPath().getPath() ); 334 TransactionManager transactionManager = base.getTransactionManager(); 335 transactionManager.setMaximumTransactionsInLog( 2000 ); 336 } 337 catch ( IOException ioe ) 338 { 339 throw new LdapOtherException( ioe.getMessage(), ioe ); 340 } 341 342 // Find the underlying directories 343 File partitionDir = new File( getPartitionPath() ); 344 345 // get the names of the db files 346 List<String> indexDbFileNameList = Arrays.asList( partitionDir.list( DB_FILTER ) ); 347 348 // then add all index objects to a list 349 List<String> allIndices = new ArrayList<>(); 350 351 try 352 { 353 // Iterate on the declared indexes, deleting the old ones 354 for ( Index<?, String> index : getIndexedAttributes() ) 355 { 356 // Index won't be initialized at this time, so lookup AT registry to get the OID 357 AttributeType indexAT = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ); 358 String oid = indexAT.getOid(); 359 allIndices.add( oid ); 360 361 // take the part after removing .db from the 362 String name = oid + JDBM_DB_FILE_EXTN; 363 364 // if the name doesn't exist in the list of index DB files 365 // this is a new index and we need to build it 366 if ( indexDbFileNameList.contains( name ) ) 367 { 368 ( ( JdbmIndex<?> ) index ).close( null ); 369 370 File indexFile = new File( partitionDir, name ); 371 indexFile.delete(); 372 373 // Recreate the index 374 ( ( JdbmIndex<?> ) index ).init( base, schemaManager, indexAT ); 375 } 376 } 377 // Ok, now, rebuild the indexes. 378 int masterTableCount = rebuildIndexes( null ); 379 380 // Now that the RdnIndex has been rebuilt, we have to update the nbChildren and nbDescendants values 381 // We loop again on the MasterTable 382 updateRdnIndexCounters( null ); 383 384 // Flush the indexes on disk 385 sync(); 386 387 System.out.println( "Total entries present in the partition " + masterTableCount ); 388 System.out.println( "Repair complete" ); 389 } 390 catch ( IOException ioe ) 391 { 392 throw new LdapOtherException( ioe.getMessage(), ioe ); 393 } 394 } 395 396 397 @Override 398 protected void doInit() throws LdapException 399 { 400 if ( !initialized ) 401 { 402 BaseRecordManager base; 403 404 // setup optimizer and registries for parent 405 if ( !optimizerEnabled ) 406 { 407 setOptimizer( new NoOpOptimizer() ); 408 } 409 else 410 { 411 setOptimizer( new DefaultOptimizer( this ) ); 412 } 413 414 EvaluatorBuilder evaluatorBuilder = new EvaluatorBuilder( this, schemaManager ); 415 CursorBuilder cursorBuilder = new CursorBuilder( this, evaluatorBuilder ); 416 417 setSearchEngine( new DefaultSearchEngine( this, cursorBuilder, evaluatorBuilder, getOptimizer() ) ); 418 419 // Create the underlying directories (only if needed) 420 File partitionDir = new File( getPartitionPath() ); 421 422 if ( !partitionDir.exists() && !partitionDir.mkdirs() ) 423 { 424 throw new LdapOtherException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, partitionDir ) ); 425 } 426 427 // First, check if the file storing the data exists 428 String path = partitionDir.getPath() + File.separator + id; 429 430 try 431 { 432 base = new BaseRecordManager( path ); 433 TransactionManager transactionManager = base.getTransactionManager(); 434 transactionManager.setMaximumTransactionsInLog( 2000 ); 435 436 // prevent the OOM when more than 50k users are loaded at a stretch 437 // adding this system property to make it configurable till JDBM gets replaced by Mavibot 438 String cacheSizeVal = System.getProperty( "jdbm.recman.cache.size", "100" ); 439 440 int recCacheSize = Integer.parseInt( cacheSizeVal ); 441 442 LOG.info( "Setting CacheRecondManager's cache size to {}", recCacheSize ); 443 444 recMan = new CacheRecordManager( base, new MRU( recCacheSize ) ); 445 } 446 catch ( IOException ioe ) 447 { 448 throw new LdapOtherException( ioe.getMessage(), ioe ); 449 } 450 451 // Iterate on the declared indexes 452 List<String> allIndices = new ArrayList<>(); 453 List<Index<?, String>> indexToBuild = new ArrayList<>(); 454 455 for ( Index<?, String> index : getIndexedAttributes() ) 456 { 457 String oid = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ).getOid(); 458 allIndices.add( oid ); 459 460 // if the name doesn't exist in the database 461 // this is a new index and we need to build it 462 try 463 { 464 // Check the forward index only (we suppose we never will add a reverse index later on) 465 String forwardIndex = oid + "_forward"; 466 467 if ( recMan.getNamedObject( forwardIndex ) == 0 ) 468 { 469 // The index does not exist in the database, we need to build it 470 indexToBuild.add( index ); 471 } 472 } 473 catch ( IOException ioe ) 474 { 475 throw new LdapOtherException( ioe.getMessage(), ioe ); 476 } 477 } 478 479 /* 480 // get all index db files first 481 File[] allIndexDbFiles = partitionDir.listFiles( DB_FILTER ); 482 483 // get the names of the db files also 484 List<String> indexDbFileNameList = Arrays.asList( partitionDir.list( DB_FILTER ) ); 485 486 // then add all index objects to a list 487 List<String> allIndices = new ArrayList<>(); 488 489 List<Index<?, String>> indexToBuild = new ArrayList<>(); 490 491 // Iterate on the declared indexes 492 for ( Index<?, String> index : getIndexedAttributes() ) 493 { 494 // Index won't be initialized at this time, so lookup AT registry to get the OID 495 String oid = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ).getOid(); 496 allIndices.add( oid ); 497 498 // take the part after removing .db from the 499 String name = oid + JDBM_DB_FILE_EXTN; 500 501 // if the name doesn't exist in the list of index DB files 502 // this is a new index and we need to build it 503 if ( !indexDbFileNameList.contains( name ) ) 504 { 505 indexToBuild.add( index ); 506 } 507 } 508 */ 509 510 // Initialize the indexes 511 super.doInit(); 512 513 if ( cacheSize < 0 ) 514 { 515 cacheSize = DEFAULT_CACHE_SIZE; 516 LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id ); 517 } 518 else 519 { 520 LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id ); 521 } 522 523 // Create the master table (the table containing all the entries) 524 try 525 { 526 master = new JdbmMasterTable( recMan, schemaManager ); 527 } 528 catch ( IOException ioe ) 529 { 530 throw new LdapOtherException( ioe.getMessage(), ioe ); 531 } 532 533 if ( !indexToBuild.isEmpty() ) 534 { 535 buildUserIndex( beginReadTransaction(), indexToBuild ); 536 } 537 538 entryCache = Caffeine.newBuilder().maximumSize( cacheSize ).build(); 539 540 // Initialization of the context entry 541 if ( ( suffixDn != null ) && ( contextEntry != null ) ) 542 { 543 Dn contextEntryDn = contextEntry.getDn(); 544 545 // Checking if the context entry DN is schema aware 546 if ( !contextEntryDn.isSchemaAware() ) 547 { 548 contextEntryDn = new Dn( schemaManager, contextEntryDn ); 549 } 550 551 // We're only adding the entry if the two DNs are equal 552 if ( suffixDn.equals( contextEntryDn ) ) 553 { 554 // Looking for the current context entry 555 Entry suffixEntry; 556 LookupOperationContext lookupContext = new LookupOperationContext( null, suffixDn ); 557 lookupContext.setPartition( this ); 558 559 try ( PartitionTxn partitionTxn = beginReadTransaction() ) 560 { 561 lookupContext.setTransaction( partitionTxn ); 562 suffixEntry = lookup( lookupContext ); 563 } 564 catch ( IOException ioe ) 565 { 566 throw new LdapOtherException( ioe.getMessage(), ioe ); 567 } 568 569 // We're only adding the context entry if it doesn't already exist 570 if ( suffixEntry == null ) 571 { 572 // Checking of the context entry is schema aware 573 if ( !contextEntry.isSchemaAware() ) 574 { 575 // Making the context entry schema aware 576 contextEntry = new DefaultEntry( schemaManager, contextEntry ); 577 } 578 579 // Adding the 'entryCsn' attribute 580 if ( contextEntry.get( SchemaConstants.ENTRY_CSN_AT ) == null ) 581 { 582 contextEntry.add( SchemaConstants.ENTRY_CSN_AT, new CsnFactory( 0 ).newInstance() 583 .toString() ); 584 } 585 586 // Adding the 'entryUuid' attribute 587 if ( contextEntry.get( SchemaConstants.ENTRY_UUID_AT ) == null ) 588 { 589 String uuid = UUID.randomUUID().toString(); 590 contextEntry.add( SchemaConstants.ENTRY_UUID_AT, uuid ); 591 } 592 593 // And add this entry to the underlying partition 594 PartitionTxn partitionTxn = null; 595 AddOperationContext addContext = new AddOperationContext( null, contextEntry ); 596 597 try 598 { 599 partitionTxn = beginWriteTransaction(); 600 addContext.setTransaction( partitionTxn ); 601 602 add( addContext ); 603 partitionTxn.commit(); 604 } 605 catch ( LdapException le ) 606 { 607 if ( partitionTxn != null ) 608 { 609 try 610 { 611 partitionTxn.abort(); 612 } 613 catch ( IOException ioe ) 614 { 615 throw new LdapOtherException( ioe.getMessage(), ioe ); 616 } 617 } 618 619 throw le; 620 } 621 catch ( IOException ioe ) 622 { 623 try 624 { 625 partitionTxn.abort(); 626 } 627 catch ( IOException ioe2 ) 628 { 629 throw new LdapOtherException( ioe2.getMessage(), ioe2 ); 630 } 631 632 throw new LdapOtherException( ioe.getMessage(), ioe ); 633 } 634 } 635 } 636 } 637 638 // We are done ! 639 initialized = true; 640 } 641 } 642 643 644 /** 645 * {@inheritDoc}} 646 */ 647 public String getDefaultId() 648 { 649 return Partition.DEFAULT_ID; 650 } 651 652 653 /** 654 * {@inheritDoc} 655 */ 656 public String getRootId() 657 { 658 return Partition.ROOT_ID; 659 } 660 661 662 /** 663 * This method is called when the synch thread is waking up, to write 664 * the modified data. 665 * 666 * @throws LdapException on failures to sync database files to disk 667 */ 668 @Override 669 public synchronized void sync() throws LdapException 670 { 671 if ( !initialized ) 672 { 673 return; 674 } 675 676 try 677 { 678 // Commit 679 recMan.commit(); 680 681 // And flush the journal 682 BaseRecordManager baseRecordManager = null; 683 684 if ( recMan instanceof CacheRecordManager ) 685 { 686 baseRecordManager = ( ( BaseRecordManager ) ( ( CacheRecordManager ) recMan ).getRecordManager() ); 687 } 688 else 689 { 690 baseRecordManager = ( ( BaseRecordManager ) recMan ); 691 } 692 693 baseRecordManager.getTransactionManager().synchronizeLog(); 694 } 695 catch ( IOException ioe ) 696 { 697 throw new LdapOtherException( ioe.getMessage(), ioe ); 698 } 699 } 700 701 702 /** 703 * Builds user defined indexes on a attributes by browsing all the entries present in master db 704 * 705 * Note: if the given list of indices contains any system index that will be skipped. 706 * 707 * WARN: MUST be called after calling super.doInit() 708 * 709 * @param indices then selected indexes that need to be built 710 * @throws Exception in case of any problems while building the index 711 */ 712 private void buildUserIndex( PartitionTxn partitionTxn, List<Index<?, String>> indices ) throws LdapException 713 { 714 try 715 { 716 Cursor<Tuple<String, Entry>> cursor = master.cursor(); 717 cursor.beforeFirst(); 718 719 while ( cursor.next() ) 720 { 721 for ( Index index : indices ) 722 { 723 AttributeType atType = index.getAttribute(); 724 725 String attributeOid = index.getAttribute().getOid(); 726 727 if ( systemIndices.get( attributeOid ) != null ) 728 { 729 // skipping building of the system index 730 continue; 731 } 732 733 LOG.info( "building the index for attribute type {}", atType ); 734 735 Tuple<String, Entry> tuple = cursor.get(); 736 737 String id = tuple.getKey(); 738 Entry entry = tuple.getValue(); 739 740 Attribute entryAttr = entry.get( atType ); 741 742 if ( entryAttr != null ) 743 { 744 for ( Value value : entryAttr ) 745 { 746 index.add( partitionTxn, value.getString(), id ); 747 } 748 749 // Adds only those attributes that are indexed 750 presenceIdx.add( partitionTxn, attributeOid, id ); 751 } 752 } 753 } 754 755 cursor.close(); 756 } 757 catch ( CursorException | IOException e ) 758 { 759 throw new LdapOtherException( e.getMessage(), e ); 760 } 761 } 762 763 764 /** 765 * removes any unused/removed attribute index files present under the partition's 766 * working directory 767 */ 768 private void deleteUnusedIndexFiles( List<String> allIndices, File[] dbFiles ) 769 { 770 for ( File file : dbFiles ) 771 { 772 String name = file.getName(); 773 // take the part after removing .db from the 774 name = name.substring( 0, name.lastIndexOf( JDBM_DB_FILE_EXTN ) ); 775 776 if ( systemIndices.get( name ) != null ) 777 { 778 // do not delete the system index file 779 continue; 780 } 781 782 // remove the file if not found in the list of names of indices 783 if ( !allIndices.contains( name ) ) 784 { 785 boolean deleted = file.delete(); 786 787 if ( deleted ) 788 { 789 LOG.info( "Deleted unused index file {}", file.getAbsolutePath() ); 790 } 791 else 792 { 793 LOG.warn( "Failed to delete unused index file {}", file.getAbsolutePath() ); 794 } 795 } 796 } 797 } 798 799 800 /** 801 * {@inheritDoc} 802 */ 803 @Override 804 protected Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException 805 { 806 JdbmIndex<?> jdbmIndex; 807 808 if ( index instanceof JdbmRdnIndex ) 809 { 810 jdbmIndex = ( JdbmRdnIndex ) index; 811 } 812 else if ( index instanceof JdbmDnIndex ) 813 { 814 jdbmIndex = ( JdbmDnIndex ) index; 815 } 816 else if ( index instanceof JdbmIndex<?> ) 817 { 818 jdbmIndex = ( JdbmIndex<?> ) index; 819 } 820 else 821 { 822 LOG.debug( "Supplied index {} is not a JdbmIndex. " 823 + "Will create new JdbmIndex using copied configuration parameters.", index ); 824 jdbmIndex = new JdbmIndex( index.getAttributeId(), true ); 825 jdbmIndex.setCacheSize( index.getCacheSize() ); 826 jdbmIndex.setNumDupLimit( JdbmIndex.DEFAULT_DUPLICATE_LIMIT ); 827 } 828 829 try 830 { 831 jdbmIndex.init( recMan, schemaManager, schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ) ); 832 } 833 catch ( IOException ioe ) 834 { 835 throw new LdapOtherException( ioe.getMessage(), ioe ); 836 } 837 838 return jdbmIndex; 839 } 840 841 842 /** 843 * {@inheritDoc} 844 */ 845 @Override 846 protected synchronized void doDestroy( PartitionTxn partitionTxn ) throws LdapException 847 { 848 MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) ); 849 850 if ( !initialized ) 851 { 852 return; 853 } 854 855 try 856 { 857 super.doDestroy( partitionTxn ); 858 } 859 catch ( Exception e ) 860 { 861 errors.addThrowable( e ); 862 } 863 864 // This is specific to the JDBM store : close the record manager 865 try 866 { 867 recMan.close(); 868 LOG.debug( "Closed record manager for {} partition.", suffixDn ); 869 } 870 catch ( IOException t ) 871 { 872 LOG.error( I18n.err( I18n.ERR_127 ), t ); 873 errors.addThrowable( t ); 874 } 875 finally 876 { 877 if ( entryCache != null ) 878 { 879 entryCache.invalidateAll(); 880 } 881 } 882 883 if ( errors.size() > 0 ) 884 { 885 throw new LdapOtherException( errors.getMessage(), errors ); 886 } 887 } 888 889 890 /** 891 * {@inheritDoc} 892 */ 893 @Override 894 protected final Index createSystemIndex( String oid, URI path, boolean withReverse ) throws LdapException 895 { 896 LOG.debug( "Supplied index {} is not a JdbmIndex. " 897 + "Will create new JdbmIndex using copied configuration parameters." ); 898 JdbmIndex<?> jdbmIndex; 899 900 if ( oid.equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) ) 901 { 902 jdbmIndex = new JdbmRdnIndex(); 903 jdbmIndex.setAttributeId( ApacheSchemaConstants.APACHE_RDN_AT_OID ); 904 jdbmIndex.setNumDupLimit( JdbmIndex.DEFAULT_DUPLICATE_LIMIT ); 905 } 906 else if ( oid.equals( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ) ) 907 { 908 jdbmIndex = new JdbmDnIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 909 jdbmIndex.setAttributeId( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); 910 jdbmIndex.setNumDupLimit( JdbmIndex.DEFAULT_DUPLICATE_LIMIT ); 911 } 912 else 913 { 914 jdbmIndex = new JdbmIndex( oid, withReverse ); 915 jdbmIndex.setNumDupLimit( JdbmIndex.DEFAULT_DUPLICATE_LIMIT ); 916 } 917 918 jdbmIndex.setWkDirPath( path ); 919 920 return jdbmIndex; 921 } 922 923 924 @Override 925 public void updateCache( OperationContext opCtx ) 926 { 927 if ( entryCache == null ) 928 { 929 return; 930 } 931 932 try 933 { 934 if ( opCtx instanceof ModifyOperationContext ) 935 { 936 // replace the entry 937 ModifyOperationContext modCtx = ( ModifyOperationContext ) opCtx; 938 Entry entry = modCtx.getAlteredEntry(); 939 String id = entry.get( SchemaConstants.ENTRY_UUID_AT ).getString(); 940 941 if ( entry instanceof ClonedServerEntry ) 942 { 943 entry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); 944 } 945 946 entryCache.put( id, entry ); 947 } 948 else if ( ( opCtx instanceof MoveOperationContext ) 949 || ( opCtx instanceof MoveAndRenameOperationContext ) 950 || ( opCtx instanceof RenameOperationContext ) ) 951 { 952 // clear the cache it is not worth updating all the children 953 entryCache.invalidateAll(); 954 } 955 else if ( opCtx instanceof DeleteOperationContext ) 956 { 957 // delete the entry 958 DeleteOperationContext delCtx = ( DeleteOperationContext ) opCtx; 959 entryCache.invalidate( delCtx.getEntry().get( SchemaConstants.ENTRY_UUID_AT ).getString() ); 960 } 961 } 962 catch ( LdapException e ) 963 { 964 LOG.warn( "Failed to update entry cache", e ); 965 } 966 } 967 968 969 @Override 970 public Entry lookupCache( String id ) 971 { 972 return ( entryCache != null ) ? entryCache.getIfPresent( id ) : null; 973 } 974 975 976 @Override 977 public void addToCache( String id, Entry entry ) 978 { 979 if ( entryCache == null ) 980 { 981 return; 982 } 983 984 Entry addedEntry = entry; 985 986 if ( entry instanceof ClonedServerEntry ) 987 { 988 addedEntry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); 989 } 990 991 entryCache.put( id, addedEntry ); 992 } 993 994 995 @Override 996 public PartitionReadTxn beginReadTransaction() 997 { 998 return new PartitionReadTxn(); 999 } 1000 1001 1002 @Override 1003 public PartitionWriteTxn beginWriteTransaction() 1004 { 1005 return new JdbmPartitionWriteTxn( recMan, isSyncOnWrite() ); 1006 } 1007}