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}