View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.partition.impl.btree.jdbm;
21  
22  
23  import java.io.File;
24  import java.io.FilenameFilter;
25  import java.io.IOException;
26  import java.net.URI;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.List;
30  import java.util.UUID;
31  
32  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
33  import org.apache.directory.api.ldap.model.csn.CsnFactory;
34  import org.apache.directory.api.ldap.model.cursor.Cursor;
35  import org.apache.directory.api.ldap.model.cursor.CursorException;
36  import org.apache.directory.api.ldap.model.cursor.Tuple;
37  import org.apache.directory.api.ldap.model.entry.Attribute;
38  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
39  import org.apache.directory.api.ldap.model.entry.Entry;
40  import org.apache.directory.api.ldap.model.entry.Value;
41  import org.apache.directory.api.ldap.model.exception.LdapException;
42  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
43  import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
44  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
45  import org.apache.directory.api.ldap.model.name.Dn;
46  import org.apache.directory.api.ldap.model.schema.AttributeType;
47  import org.apache.directory.api.ldap.model.schema.SchemaManager;
48  import org.apache.directory.api.util.exception.MultiException;
49  import org.apache.directory.server.constants.ApacheSchemaConstants;
50  import org.apache.directory.server.core.api.DnFactory;
51  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
52  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
53  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
54  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
55  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
56  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
57  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
58  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
59  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
60  import org.apache.directory.server.core.api.partition.Partition;
61  import org.apache.directory.server.core.api.partition.PartitionReadTxn;
62  import org.apache.directory.server.core.api.partition.PartitionTxn;
63  import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
64  import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
65  import org.apache.directory.server.i18n.I18n;
66  import org.apache.directory.server.xdbm.Index;
67  import org.apache.directory.server.xdbm.ParentIdAndRdn;
68  import org.apache.directory.server.xdbm.search.impl.CursorBuilder;
69  import org.apache.directory.server.xdbm.search.impl.DefaultOptimizer;
70  import org.apache.directory.server.xdbm.search.impl.DefaultSearchEngine;
71  import org.apache.directory.server.xdbm.search.impl.EvaluatorBuilder;
72  import org.apache.directory.server.xdbm.search.impl.NoOpOptimizer;
73  import org.slf4j.Logger;
74  import org.slf4j.LoggerFactory;
75  
76  import com.github.benmanes.caffeine.cache.Cache;
77  import com.github.benmanes.caffeine.cache.Caffeine;
78  
79  import jdbm.RecordManager;
80  import jdbm.helper.MRU;
81  import jdbm.recman.BaseRecordManager;
82  import jdbm.recman.CacheRecordManager;
83  import jdbm.recman.TransactionManager;
84  
85  
86  /**
87   * A {@link Partition} that stores entries in
88   * <a href="http://jdbm.sourceforge.net/">JDBM</a> database.
89   *
90   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
91   */
92  public class JdbmPartition extends AbstractBTreePartition
93  {
94      /** static logger */
95      private static final Logger LOG = LoggerFactory.getLogger( JdbmPartition.class );
96  
97      private static final String JDBM_DB_FILE_EXTN = ".db";
98  
99      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             EvaluatorBuildersearch/impl/EvaluatorBuilder.html#EvaluatorBuilder">EvaluatorBuilder evaluatorBuilder = new EvaluatorBuilder( this, schemaManager );
415             CursorBuilder/xdbm/search/impl/CursorBuilder.html#CursorBuilder">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/interceptor/context/LookupOperationContext.html#LookupOperationContext">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                         AddOperationContextre/api/interceptor/context/AddOperationContext.html#AddOperationContext">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/../../../org/apache/directory/server/core/api/interceptor/context/ModifyOperationContext.html#ModifyOperationContext">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/../../../org/apache/directory/server/core/api/interceptor/context/DeleteOperationContext.html#DeleteOperationContext">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 }