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.mavibot;
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.HashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
34  import org.apache.directory.api.ldap.model.cursor.Cursor;
35  import org.apache.directory.api.ldap.model.cursor.Tuple;
36  import org.apache.directory.api.ldap.model.entry.Attribute;
37  import org.apache.directory.api.ldap.model.entry.Entry;
38  import org.apache.directory.api.ldap.model.entry.Value;
39  import org.apache.directory.api.ldap.model.exception.LdapException;
40  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
41  import org.apache.directory.api.ldap.model.schema.AttributeType;
42  import org.apache.directory.api.ldap.model.schema.SchemaManager;
43  import org.apache.directory.api.util.exception.MultiException;
44  import org.apache.directory.mavibot.btree.RecordManager;
45  import org.apache.directory.server.constants.ApacheSchemaConstants;
46  import org.apache.directory.server.core.api.DnFactory;
47  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
48  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
51  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
52  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
53  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
54  import org.apache.directory.server.core.api.partition.Partition;
55  import org.apache.directory.server.core.api.partition.PartitionReadTxn;
56  import org.apache.directory.server.core.api.partition.PartitionTxn;
57  import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
58  import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
59  import org.apache.directory.server.i18n.I18n;
60  import org.apache.directory.server.xdbm.Index;
61  import org.apache.directory.server.xdbm.search.impl.CursorBuilder;
62  import org.apache.directory.server.xdbm.search.impl.DefaultOptimizer;
63  import org.apache.directory.server.xdbm.search.impl.DefaultSearchEngine;
64  import org.apache.directory.server.xdbm.search.impl.EvaluatorBuilder;
65  import org.apache.directory.server.xdbm.search.impl.NoOpOptimizer;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  import com.github.benmanes.caffeine.cache.Cache;
70  import com.github.benmanes.caffeine.cache.Caffeine;
71  
72  
73  /**
74   * A Mavibot partition
75   *
76   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
77   */
78  public class MavibotPartition extends AbstractBTreePartition
79  {
80      /** static logger */
81      private static final Logger LOG = LoggerFactory.getLogger( MavibotPartition.class );
82  
83      private static final String MAVIBOT_DB_FILE_EXTN = ".data";
84      
85      private static final FilenameFilter DB_FILTER = new FilenameFilter()
86      {
87  
88          public boolean accept( File dir, String name )
89          {
90              // really important to filter master.db and master.lg files
91              return ( name.endsWith( MAVIBOT_DB_FILE_EXTN ) && !name.startsWith( "master." ) );
92          }
93      };
94  
95      private RecordManager recordMan;
96  
97      /** the entry cache */
98      private Cache< String, Entry > entryCache;
99  
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             EvaluatorBuildersearch/impl/EvaluatorBuilder.html#EvaluatorBuilder">EvaluatorBuilder evaluatorBuilder = new EvaluatorBuilder( this, schemaManager );
149             CursorBuilder/xdbm/search/impl/CursorBuilder.html#CursorBuilder">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/../../../org/apache/directory/server/core/api/interceptor/context/ModifyOperationContext.html#ModifyOperationContext">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/../../../org/apache/directory/server/core/api/interceptor/context/DeleteOperationContext.html#DeleteOperationContext">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 }