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.mavibot;
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.HashSet;
030import java.util.List;
031import java.util.Set;
032
033import org.apache.directory.api.ldap.model.constants.SchemaConstants;
034import org.apache.directory.api.ldap.model.cursor.Cursor;
035import org.apache.directory.api.ldap.model.cursor.Tuple;
036import org.apache.directory.api.ldap.model.entry.Attribute;
037import org.apache.directory.api.ldap.model.entry.Entry;
038import org.apache.directory.api.ldap.model.entry.Value;
039import org.apache.directory.api.ldap.model.exception.LdapException;
040import org.apache.directory.api.ldap.model.exception.LdapOtherException;
041import org.apache.directory.api.ldap.model.schema.AttributeType;
042import org.apache.directory.api.ldap.model.schema.SchemaManager;
043import org.apache.directory.api.util.exception.MultiException;
044import org.apache.directory.mavibot.btree.RecordManager;
045import org.apache.directory.server.constants.ApacheSchemaConstants;
046import org.apache.directory.server.core.api.DnFactory;
047import org.apache.directory.server.core.api.entry.ClonedServerEntry;
048import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
049import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
050import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
051import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
052import org.apache.directory.server.core.api.interceptor.context.OperationContext;
053import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
054import org.apache.directory.server.core.api.partition.Partition;
055import org.apache.directory.server.core.api.partition.PartitionReadTxn;
056import org.apache.directory.server.core.api.partition.PartitionTxn;
057import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
058import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
059import org.apache.directory.server.i18n.I18n;
060import org.apache.directory.server.xdbm.Index;
061import org.apache.directory.server.xdbm.search.impl.CursorBuilder;
062import org.apache.directory.server.xdbm.search.impl.DefaultOptimizer;
063import org.apache.directory.server.xdbm.search.impl.DefaultSearchEngine;
064import org.apache.directory.server.xdbm.search.impl.EvaluatorBuilder;
065import org.apache.directory.server.xdbm.search.impl.NoOpOptimizer;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069import com.github.benmanes.caffeine.cache.Cache;
070import com.github.benmanes.caffeine.cache.Caffeine;
071
072
073/**
074 * A Mavibot partition
075 *
076 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
077 */
078public class MavibotPartition extends AbstractBTreePartition
079{
080    /** static logger */
081    private static final Logger LOG = LoggerFactory.getLogger( MavibotPartition.class );
082
083    private static final String MAVIBOT_DB_FILE_EXTN = ".data";
084    
085    private static final FilenameFilter DB_FILTER = new FilenameFilter()
086    {
087
088        public boolean accept( File dir, String name )
089        {
090            // really important to filter master.db and master.lg files
091            return ( name.endsWith( MAVIBOT_DB_FILE_EXTN ) && !name.startsWith( "master." ) );
092        }
093    };
094
095    private RecordManager recordMan;
096
097    /** the entry cache */
098    private Cache< String, Entry > entryCache;
099
100
101    public MavibotPartition( SchemaManager schemaManager, DnFactory dnFactory )
102    {
103        super( schemaManager, dnFactory );
104
105        MavibotEntrySerializer.setSchemaManager( schemaManager );
106
107        // Initialize the cache size
108        if ( cacheSize < 0 )
109        {
110            cacheSize = DEFAULT_CACHE_SIZE;
111            LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id );
112        }
113        else
114        {
115            LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id );
116        }
117    }
118    
119    
120    /**
121     * {@inheritDoc}
122     */
123    @Override
124    protected void doRepair() throws LdapException
125    {
126        // Nothing to do
127    }
128
129
130    /**
131     * {@inheritDoc}
132     */
133    @Override
134    protected void doInit() throws LdapException
135    {
136        if ( !initialized )
137        {
138            // setup optimizer and registries for parent
139            if ( !isOptimizerEnabled() )
140            {
141                setOptimizer( new NoOpOptimizer() );
142            }
143            else
144            {
145                setOptimizer( new DefaultOptimizer( this ) );
146            }
147
148            EvaluatorBuilder evaluatorBuilder = new EvaluatorBuilder( this, schemaManager );
149            CursorBuilder cursorBuilder = new CursorBuilder( this, evaluatorBuilder );
150
151            setSearchEngine( new DefaultSearchEngine( this, cursorBuilder, evaluatorBuilder, getOptimizer() ) );
152
153            // Create the underlying directories (only if needed)
154            File partitionDir = new File( getPartitionPath() );
155
156            if ( !partitionDir.exists() && !partitionDir.mkdirs() )
157            {
158                throw new LdapOtherException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, partitionDir ) );
159            }
160
161            if ( cacheSize < 0 )
162            {
163                cacheSize = DEFAULT_CACHE_SIZE;
164                LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, id );
165            }
166            else
167            {
168                LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, id );
169            }
170
171            recordMan = new RecordManager( partitionDir.getPath() );
172
173            // Initialize the indexes
174            super.doInit();
175
176            // First, check if the file storing the data exists
177
178            try
179            {
180                master = new MavibotMasterTable( recordMan, schemaManager, "master", cacheSize );
181            }
182            catch ( IOException ioe )
183            {
184                throw new LdapOtherException( ioe.getMessage(), ioe );
185            }
186
187            // get all index db files first
188            File[] allIndexDbFiles = partitionDir.listFiles( DB_FILTER );
189
190            // get the names of the db files also
191            List<String> indexDbFileNameList = Arrays.asList( partitionDir.list( DB_FILTER ) );
192
193            // then add all index objects to a list
194            List<String> allIndices = new ArrayList<>();
195
196            for ( Index<?, String> index : systemIndices.values() )
197            {
198                allIndices.add( index.getAttribute().getOid() );
199            }
200
201            List<Index<?, String>> indexToBuild = new ArrayList<>();
202
203            // this loop is used for two purposes
204            // one for collecting all user indices
205            // two for finding a new index to be built
206            // just to avoid another iteration for determining which is the new index
207            /* FIXME the below code needs to be modified to suit Mavibot
208                        for ( Index<?, Entry, String> index : userIndices.values() )
209                        {
210                            String indexOid = index.getAttribute().getOid();
211                            allIndices.add( indexOid );
212
213                            // take the part after removing .db from the
214                            String name = indexOid + MAVIBOT_DB_FILE_EXTN;
215
216                            // if the name doesn't exist in the list of index DB files
217                            // this is a new index and we need to build it
218                            if ( !indexDbFileNameList.contains( name ) )
219                            {
220                                indexToBuild.add( index );
221                            }
222                        }
223
224                        if ( indexToBuild.size() > 0 )
225                        {
226                            buildUserIndex( indexToBuild );
227                        }
228
229                        deleteUnusedIndexFiles( allIndices, allIndexDbFiles );
230            */
231
232            entryCache = Caffeine.newBuilder().maximumSize( cacheSize ).build();
233
234            // We are done !
235            initialized = true;
236        }
237    }
238
239
240    @Override
241    protected Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException
242    {
243        MavibotIndex<?> mavibotIndex;
244
245        if ( index instanceof MavibotRdnIndex )
246        {
247            mavibotIndex = ( MavibotRdnIndex ) index;
248        }
249        else if ( index instanceof MavibotDnIndex )
250        {
251            mavibotIndex = ( MavibotDnIndex ) index;
252        }
253        else if ( index instanceof MavibotIndex<?> )
254        {
255            mavibotIndex = ( MavibotIndex<?> ) index;
256
257            if ( mavibotIndex.getWkDirPath() == null )
258            {
259                mavibotIndex.setWkDirPath( partitionPath );
260            }
261        }
262        else
263        {
264            LOG.debug( "Supplied index {} is not a MavibotIndex.  "
265                + "Will create new MavibotIndex using copied configuration parameters.", index );
266            mavibotIndex = new MavibotIndex( index.getAttributeId(), true );
267            mavibotIndex.setCacheSize( index.getCacheSize() );
268            mavibotIndex.setWkDirPath( index.getWkDirPath() );
269        }
270
271        mavibotIndex.setRecordManager( recordMan );
272
273        try
274        {
275            mavibotIndex.init( schemaManager, schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ) );
276        }
277        catch ( IOException ioe )
278        {
279            throw new LdapOtherException( ioe.getMessage(), ioe );
280        }
281
282        return mavibotIndex;
283    }
284
285
286    /**
287     * {@inheritDoc}
288     */
289    @Override
290    protected synchronized void doDestroy( PartitionTxn partitionTxn ) throws LdapException
291    {
292        MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) );
293
294        if ( !initialized )
295        {
296            return;
297        }
298
299        try
300        {
301            super.doDestroy( partitionTxn );
302        }
303        catch ( Exception e )
304        {
305            errors.addThrowable( e );
306        }
307
308        // This is specific to the MAVIBOT store : close the record manager
309        try
310        {
311            recordMan.close();
312            LOG.debug( "Closed record manager for {} partition.", suffixDn );
313        }
314        catch ( Throwable t )
315        {
316            LOG.error( I18n.err( I18n.ERR_127 ), t );
317            errors.addThrowable( t );
318        }
319        finally
320        {
321            if ( entryCache != null )
322            {
323                entryCache.invalidateAll();
324            }
325        }
326
327        if ( errors.size() > 0 )
328        {
329            throw new LdapOtherException( errors.getMessage(), errors );
330        }
331    }
332
333
334    @Override
335    protected Index createSystemIndex( String indexOid, URI path, boolean withReverse ) throws LdapException
336    {
337        LOG.debug( "Supplied index {} is not a MavibotIndex.  "
338            + "Will create new MavibotIndex using copied configuration parameters." );
339        MavibotIndex<?> mavibotIndex;
340
341        if ( indexOid.equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) )
342        {
343            mavibotIndex = new MavibotRdnIndex();
344            mavibotIndex.setAttributeId( ApacheSchemaConstants.APACHE_RDN_AT_OID );
345        }
346        else if ( indexOid.equals( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ) )
347        {
348            mavibotIndex = new MavibotDnIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
349            mavibotIndex.setAttributeId( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
350        }
351        else
352        {
353            mavibotIndex = new MavibotIndex( indexOid, withReverse );
354        }
355
356        mavibotIndex.setWkDirPath( path );
357
358        return mavibotIndex;
359    }
360
361
362    /**jdbm
363     * removes any unused/removed attribute index files present under the partition's
364     * working directory
365     */
366    private void deleteUnusedIndexFiles( List<String> allIndices, File[] dbFiles )
367    {
368
369    }
370
371
372    /**
373     * Builds user defined indexes on a attributes by browsing all the entries present in master db
374     * 
375     * @param userIndexes then user defined indexes to create
376     * @throws Exception in case of any problems while building the index
377     */
378    private void buildUserIndex( PartitionTxn partitionTxn, List<Index<?, String>> userIndexes ) throws Exception
379    {
380        Cursor<Tuple<String, Entry>> cursor = master.cursor();
381        cursor.beforeFirst();
382
383        while ( cursor.next() )
384        {
385            for ( Index index : userIndexes )
386            {
387                AttributeType atType = index.getAttribute();
388
389                String attributeOid = index.getAttribute().getOid();
390
391                LOG.info( "building the index for attribute type {}", atType );
392
393                Tuple<String, Entry> tuple = cursor.get();
394
395                String id = tuple.getKey();
396                Entry entry = tuple.getValue();
397
398                Attribute entryAttr = entry.get( atType );
399
400                if ( entryAttr != null )
401                {
402                    for ( Value value : entryAttr )
403                    {
404                        index.add( partitionTxn, value.getString(), id );
405                    }
406
407                    // Adds only those attributes that are indexed
408                    presenceIdx.add( partitionTxn, attributeOid, id );
409                }
410            }
411        }
412
413        cursor.close();
414    }
415
416
417    /**
418     * {@inheritDoc}}
419     */
420    public String getDefaultId()
421    {
422        return Partition.DEFAULT_ID;
423    }
424
425
426    /**
427     * {@inheritDoc}
428     */
429    public String getRootId()
430    {
431        return Partition.ROOT_ID;
432    }
433
434
435    public RecordManager getRecordMan()
436    {
437        return recordMan;
438    }
439
440
441    /**
442     * {@inheritDoc}
443     */
444    @Override
445    public Entry lookupCache( String id )
446    {
447        return ( entryCache != null ) ? entryCache.getIfPresent( id ) : null;
448    }
449
450
451    @Override
452    public void addToCache( String id, Entry entry )
453    {
454        if ( entryCache == null )
455        {
456            return;
457        }
458
459        if ( entry instanceof ClonedServerEntry )
460        {
461            entry = ( ( ClonedServerEntry ) entry ).getOriginalEntry();
462        }
463
464        entryCache.put( id, entry );
465    }
466
467
468    @Override
469    public void updateCache( OperationContext opCtx )
470    {
471        if ( entryCache == null )
472        {
473            return;
474        }
475
476        try
477        {
478            if ( opCtx instanceof ModifyOperationContext )
479            {
480                // replace the entry
481                ModifyOperationContext modCtx = ( ModifyOperationContext ) opCtx;
482                Entry entry = modCtx.getAlteredEntry();
483                String id = entry.get( SchemaConstants.ENTRY_UUID_AT ).getString();
484
485                if ( entry instanceof ClonedServerEntry )
486                {
487                    entry = ( ( ClonedServerEntry ) entry ).getOriginalEntry();
488                }
489
490                entryCache.put( id, entry );
491            }
492            else if ( ( opCtx instanceof MoveOperationContext ) || ( opCtx instanceof MoveAndRenameOperationContext )
493                || ( opCtx instanceof RenameOperationContext ) )
494            {
495                // clear the cache it is not worth updating all the children
496                entryCache.invalidateAll();
497            }
498            else if ( opCtx instanceof DeleteOperationContext )
499            {
500                // delete the entry
501                DeleteOperationContext delCtx = ( DeleteOperationContext ) opCtx;
502                entryCache.invalidate( delCtx.getEntry().get( SchemaConstants.ENTRY_UUID_AT ).getString() );
503            }
504        }
505        catch ( LdapException e )
506        {
507            LOG.warn( "Failed to update entry cache", e );
508        }
509    }
510
511    
512    /**
513     * @return The set of system and user indexes
514     */
515    public Set<Index<?, String>> getAllIndices()
516    {
517        Set<Index<?, String>> all = new HashSet<>( systemIndices.values() );
518        all.addAll( userIndices.values() );
519        
520        return all;
521    }
522
523
524    @Override
525    public PartitionReadTxn beginReadTransaction()
526    {
527        return new PartitionReadTxn();
528    }
529
530
531    @Override
532    public PartitionWriteTxn beginWriteTransaction()
533    {
534        return new PartitionWriteTxn();
535    }
536}