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.mavibot.btree.managed;
21  
22  
23  import java.io.Closeable;
24  import java.io.IOException;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.FileChannel;
27  import java.util.Comparator;
28  import java.util.LinkedList;
29  import java.util.concurrent.ConcurrentLinkedQueue;
30  import java.util.concurrent.locks.ReentrantLock;
31  
32  import net.sf.ehcache.Cache;
33  import net.sf.ehcache.config.CacheConfiguration;
34  
35  import org.apache.directory.mavibot.btree.BTreeHeader;
36  import org.apache.directory.mavibot.btree.Tuple;
37  import org.apache.directory.mavibot.btree.TupleCursor;
38  import org.apache.directory.mavibot.btree.ValueCursor;
39  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
40  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  
45  /**
46   * The B+Tree MVCC data structure.
47   * 
48   * @param <K> The type for the keys
49   * @param <V> The type for the stored values
50   *
51   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
52   */
53  public class BTree<K, V> implements Closeable
54  {
55      /** The LoggerFactory used by this class */
56      protected static final Logger LOG = LoggerFactory.getLogger( BTree.class );
57  
58      /** The Header for a managed BTree */
59      private BTreeHeader btreeHeader;
60  
61      /** Default page size (number of entries per node) */
62      public static final int DEFAULT_PAGE_SIZE = 16;
63  
64      /** Default size of the buffer used to write data on disk. Around 1Mb */
65      public static final int DEFAULT_WRITE_BUFFER_SIZE = 4096 * 250;
66  
67      /** The default journal name */
68      public static final String DEFAULT_JOURNAL = "mavibot.log";
69  
70      /** The default data file suffix */
71      public static final String DATA_SUFFIX = ".db";
72  
73      /** The default journal file suffix */
74      public static final String JOURNAL_SUFFIX = ".log";
75  
76      /** Comparator used to index entries. */
77      private Comparator<K> comparator;
78  
79      /** The current rootPage */
80      protected volatile Page<K, V> rootPage;
81  
82      /** The list of read transactions being executed */
83      private ConcurrentLinkedQueue<Transaction<K, V>> readTransactions;
84  
85      /** The size of the buffer used to write data in disk */
86      private int writeBufferSize;
87  
88      /** The Key serializer used for this tree.*/
89      private ElementSerializer<K> keySerializer;
90  
91      /** The Value serializer used for this tree. */
92      private ElementSerializer<V> valueSerializer;
93  
94      /** The RecordManager if the BTree is managed */
95      private RecordManager recordManager;
96  
97      /** A lock used to protect the write operation against concurrent access */
98      private ReentrantLock writeLock;
99  
100     /** The thread responsible for the cleanup of timed out reads */
101     private Thread readTransactionsThread;
102 
103     /** Define a default delay for a read transaction. This is 10 seconds */
104     public static final long DEFAULT_READ_TIMEOUT = 10 * 1000L;
105 
106     /** The read transaction timeout */
107     private long readTimeOut = DEFAULT_READ_TIMEOUT;
108 
109     /** The cache associated with this BTree */
110     private Cache cache;
111 
112     /** The cache size, default to 1000 elements */
113     private int cacheSize = DEFAULT_CACHE_SIZE;
114 
115     /** The default number of pages to keep in memory */
116     private static final int DEFAULT_CACHE_SIZE = 1000;
117 
118     /** The number of stored Values before we switch to a BTree */
119     private static final int DEFAULT_VALUE_THRESHOLD_UP = 8;
120 
121     /** The number of stored Values before we switch back to an array */
122     private static final int DEFAULT_VALUE_THRESHOLD_LOW = 1;
123 
124     /** The configuration for the array <-> BTree switch */
125     /*No qualifier*/static int valueThresholdUp = DEFAULT_VALUE_THRESHOLD_UP;
126     /*No qualifier*/static int valueThresholdLow = DEFAULT_VALUE_THRESHOLD_LOW;
127 
128 
129     /**
130      * Create a thread that is responsible of cleaning the transactions when
131      * they hit the timeout
132      */
133     private void createTransactionManager()
134     {
135         Runnable readTransactionTask = new Runnable()
136         {
137             public void run()
138             {
139                 try
140                 {
141                     Transaction<K, V> transaction = null;
142 
143                     while ( !Thread.currentThread().isInterrupted() )
144                     {
145                         long timeoutDate = System.currentTimeMillis() - readTimeOut;
146                         long t0 = System.currentTimeMillis();
147                         int nbTxns = 0;
148 
149                         // Loop on all the transactions from the queue
150                         while ( ( transaction = readTransactions.peek() ) != null )
151                         {
152                             nbTxns++;
153 
154                             if ( transaction.isClosed() )
155                             {
156                                 // The transaction is already closed, remove it from the queue
157                                 readTransactions.poll();
158                                 continue;
159                             }
160 
161                             // Check if the transaction has timed out
162                             if ( transaction.getCreationDate() < timeoutDate )
163                             {
164                                 transaction.close();
165                                 readTransactions.poll();
166                                 continue;
167                             }
168 
169                             // We need to stop now
170                             break;
171                         }
172 
173                         long t1 = System.currentTimeMillis();
174 
175                         if ( nbTxns > 0 )
176                         {
177                             System.out.println( "Processing old txn : " + nbTxns + ", " + ( t1 - t0 ) + "ms" );
178                         }
179 
180                         // Wait until we reach the timeout
181                         Thread.sleep( readTimeOut );
182                     }
183                 }
184                 catch ( InterruptedException ie )
185                 {
186                     //System.out.println( "Interrupted" );
187                 }
188                 catch ( Exception e )
189                 {
190                     throw new RuntimeException( e );
191                 }
192             }
193         };
194 
195         readTransactionsThread = new Thread( readTransactionTask );
196         readTransactionsThread.setDaemon( true );
197         readTransactionsThread.start();
198     }
199 
200 
201     /**
202      * Creates a new BTree, with no initialization. 
203      */
204     public BTree()
205     {
206         btreeHeader = new BTreeHeader();
207     }
208 
209 
210     /**
211      * Creates a new in-memory BTree using the BTreeConfiguration to initialize the 
212      * BTree
213      * 
214      * @param comparator The comparator to use
215      */
216     public BTree( BTreeConfiguration<K, V> configuration ) throws IOException
217     {
218         String name = configuration.getName();
219 
220         if ( name == null )
221         {
222             throw new IllegalArgumentException( "BTree name cannot be null" );
223         }
224 
225         btreeHeader = new BTreeHeader();
226         btreeHeader.setName( name );
227         btreeHeader.setPageSize( configuration.getPageSize() );
228 
229         keySerializer = configuration.getKeySerializer();
230         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
231 
232         valueSerializer = configuration.getValueSerializer();
233         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
234 
235         comparator = keySerializer.getComparator();
236         readTimeOut = configuration.getReadTimeOut();
237         writeBufferSize = configuration.getWriteBufferSize();
238         btreeHeader.setAllowDuplicates( configuration.isAllowDuplicates() );
239         cacheSize = configuration.getCacheSize();
240 
241         if ( comparator == null )
242         {
243             throw new IllegalArgumentException( "Comparator should not be null" );
244         }
245 
246         // Create the first root page, with revision 0L. It will be empty
247         // and increment the revision at the same time
248         rootPage = new Leaf<K, V>( this );
249 
250         // Now, initialize the BTree
251         init();
252     }
253 
254 
255     /**
256      * Creates a new in-memory BTree with a default page size and key/value serializers.
257      * 
258      * @param comparator The comparator to use
259      */
260     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer )
261         throws IOException
262     {
263         this( name, keySerializer, valueSerializer, false );
264     }
265 
266 
267     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer,
268         boolean allowDuplicates )
269         throws IOException
270     {
271         this( name, null, keySerializer, valueSerializer, DEFAULT_PAGE_SIZE, allowDuplicates, DEFAULT_CACHE_SIZE );
272     }
273 
274 
275     /**
276      * Creates a new in-memory BTree with a default page size and key/value serializers.
277      * 
278      * @param comparator The comparator to use
279      */
280     public BTree( String name, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer, int pageSize )
281         throws IOException
282     {
283         this( name, null, keySerializer, valueSerializer, pageSize );
284     }
285 
286 
287     /**
288      * Creates a new BTree with a default page size and a comparator, with an associated file.
289      * @param comparator The comparator to use
290      * @param serializer The serializer to use
291      */
292     public BTree( String name, String path, ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer )
293         throws IOException
294     {
295         this( name, path, keySerializer, valueSerializer, DEFAULT_PAGE_SIZE );
296     }
297 
298 
299     /**
300      * 
301      * Creates a new instance of BTree with the given name and store it under the given dataDir if provided.
302      *
303      * @param name the name of the BTree
304      * @param dataDir the name of the data directory with absolute path
305      * @param keySerializer key serializer
306      * @param valueSerializer value serializer
307      * @param pageSize size of the page
308      * @throws IOException
309      */
310     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
311         ElementSerializer<V> valueSerializer,
312         int pageSize )
313         throws IOException
314     {
315         this( name, dataDir, keySerializer, valueSerializer, pageSize, false, DEFAULT_CACHE_SIZE );
316     }
317 
318 
319     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
320         ElementSerializer<V> valueSerializer,
321         int pageSize, boolean allowDuplicates )
322         throws IOException
323     {
324         this( name, dataDir, keySerializer, valueSerializer, pageSize, allowDuplicates, DEFAULT_CACHE_SIZE );
325     }
326 
327 
328     public BTree( String name, String dataDir, ElementSerializer<K> keySerializer,
329         ElementSerializer<V> valueSerializer,
330         int pageSize, boolean allowDuplicates, int cacheSize )
331         throws IOException
332     {
333         btreeHeader = new BTreeHeader();
334         btreeHeader.setName( name );
335 
336         setPageSize( pageSize );
337         writeBufferSize = DEFAULT_WRITE_BUFFER_SIZE;
338 
339         this.cacheSize = cacheSize;
340 
341         this.keySerializer = keySerializer;
342 
343         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
344 
345         this.valueSerializer = valueSerializer;
346 
347         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
348 
349         comparator = keySerializer.getComparator();
350 
351         btreeHeader.setAllowDuplicates( allowDuplicates );
352 
353         // Create the first root page, with revision 0L. It will be empty
354         // and increment the revision at the same time
355         rootPage = new Leaf<K, V>( this );
356 
357         // Now, call the init() method
358         init();
359     }
360 
361 
362     /**
363      * Initialize the BTree.
364      * 
365      * @throws IOException If we get some exception while initializing the BTree
366      */
367     public void init() throws IOException
368     {
369         // Create the queue containing the pending read transactions
370         readTransactions = new ConcurrentLinkedQueue<Transaction<K, V>>();
371 
372         writeLock = new ReentrantLock();
373 
374         // Initialize the caches
375         CacheConfiguration cacheConfiguration = new CacheConfiguration();
376         cacheConfiguration.setName( "pages" );
377         cacheConfiguration.setEternal( true );
378         cacheConfiguration.setOverflowToDisk( false );
379         cacheConfiguration.setCacheLoaderTimeoutMillis( 0 );
380         cacheConfiguration.setMaxElementsInMemory( cacheSize );
381         cacheConfiguration.setMemoryStoreEvictionPolicy( "LRU" );
382 
383         cache = new Cache( cacheConfiguration );
384         cache.initialise();
385 
386         // Initialize the txnManager thread
387         //FIXME we should NOT create a new transaction manager thread for each BTree
388         //createTransactionManager();
389     }
390 
391 
392     /**
393      * Return the cache we use in this BTree
394      */
395     /* No qualifier */Cache getCache()
396     {
397         return cache;
398     }
399 
400 
401     /**
402      * Close the BTree, cleaning up all the data structure
403      */
404     public void close() throws IOException
405     {
406         // Stop the readTransaction thread
407         // readTransactionsThread.interrupt();
408         // readTransactions.clear();
409 
410         rootPage = null;
411     }
412 
413 
414     /**
415      * @return the btreeOffset
416      */
417     /* No qualifier*/long getBtreeOffset()
418     {
419         return btreeHeader.getBTreeOffset();
420     }
421 
422 
423     /**
424      * @param btreeOffset the btreeOffset to set
425      */
426     /* No qualifier*/void setBtreeOffset( long btreeOffset )
427     {
428         btreeHeader.setBTreeOffset( btreeOffset );
429     }
430 
431 
432     /**
433      * @return the rootPageOffset
434      */
435     /* No qualifier*/long getRootPageOffset()
436     {
437         return btreeHeader.getRootPageOffset();
438     }
439 
440 
441     /**
442      * @param rootPageOffset the rootPageOffset to set
443      */
444     /* No qualifier*/void setRootPageOffset( long rootPageOffset )
445     {
446         btreeHeader.setRootPageOffset( rootPageOffset );
447     }
448 
449 
450     /**
451      * @return the nextBTreeOffset
452      */
453     /* No qualifier*/long getNextBTreeOffset()
454     {
455         return btreeHeader.getNextBTreeOffset();
456     }
457 
458 
459     /**
460      * @param nextBTreeOffset the nextBTreeOffset to set
461      */
462     /* No qualifier*/void setNextBTreeOffset( long nextBTreeOffset )
463     {
464         btreeHeader.setNextBTreeOffset( nextBTreeOffset );
465     }
466 
467 
468     /**
469      * Gets the number which is a power of 2 immediately above the given positive number.
470      */
471     private int getPowerOf2( int size )
472     {
473         int newSize = --size;
474         newSize |= newSize >> 1;
475         newSize |= newSize >> 2;
476         newSize |= newSize >> 4;
477         newSize |= newSize >> 8;
478         newSize |= newSize >> 16;
479         newSize++;
480 
481         return newSize;
482     }
483 
484 
485     /**
486      * Set the maximum number of elements we can store in a page. This must be a
487      * number greater than 1, and a power of 2. The default page size is 16.
488      * <br/>
489      * If the provided size is below 2, we will default to DEFAULT_PAGE_SIZE.<br/>
490      * If the provided size is not a power of 2, we will select the closest power of 2
491      * higher than the given number<br/>
492      * 
493      * @param pageSize The requested page size
494      */
495     public void setPageSize( int pageSize )
496     {
497         if ( pageSize <= 2 )
498         {
499             btreeHeader.setPageSize( DEFAULT_PAGE_SIZE );
500         }
501         else
502         {
503             btreeHeader.setPageSize( getPowerOf2( pageSize ) );
504         }
505     }
506 
507 
508     /**
509      * Set the new root page for this tree. Used for debug purpose only. The revision
510      * will always be 0;
511      * 
512      * @param root the new root page.
513      */
514     /* No qualifier */void setRoot( Page<K, V> root )
515     {
516         rootPage = root;
517     }
518 
519 
520     /**
521      * Gets the RecordManager for a managed BTree
522      * 
523      * @return The recordManager if the BTree is managed
524      */
525     /* No qualifier */RecordManager getRecordManager()
526     {
527         return recordManager;
528     }
529 
530 
531     /**
532      * Inject a RecordManager for a managed BTree
533      * 
534      * @param recordManager The injected RecordManager
535      */
536     /* No qualifier */void setRecordManager( RecordManager recordManager )
537     {
538         this.recordManager = recordManager;
539     }
540 
541 
542     /**
543      * @return the pageSize
544      */
545     public int getPageSize()
546     {
547         return btreeHeader.getPageSize();
548     }
549 
550 
551     /**
552      * Generates a new revision number. It's only used by the Page instances.
553      * 
554      * @return a new incremental revision number
555      */
556     /** No qualifier */
557     long generateRevision()
558     {
559         return btreeHeader.incrementRevision();
560     }
561 
562 
563     /**
564      * Insert an entry in the BTree.
565      * <p>
566      * We will replace the value if the provided key already exists in the
567      * btree.
568      *
569      * @param key Inserted key
570      * @param value Inserted value
571      * @return Existing value, if any.
572      * @throws IOException TODO
573      */
574     public V insert( K key, V value ) throws IOException
575     {
576         long revision = generateRevision();
577 
578         V existingValue = null;
579 
580         try
581         {
582             // Commented atm, we will have to play around the idea of transactions later
583             writeLock.lock();
584 
585             InsertResult<K, V> result = insert( key, value, revision );
586 
587             if ( result instanceof ModifyResult )
588             {
589                 existingValue = ( ( ModifyResult<K, V> ) result ).getModifiedValue();
590             }
591         }
592         finally
593         {
594             // See above
595             writeLock.unlock();
596         }
597 
598         return existingValue;
599     }
600 
601 
602     /**
603      * Delete the entry which key is given as a parameter. If the entry exists, it will
604      * be removed from the tree, the old tuple will be returned. Otherwise, null is returned.
605      * 
606      * @param key The key for the entry we try to remove
607      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
608      */
609     public Tuple<K, V> delete( K key ) throws IOException
610     {
611         if ( key == null )
612         {
613             throw new IllegalArgumentException( "Key must not be null" );
614         }
615 
616         long revision = generateRevision();
617 
618         Tuple<K, V> deleted = delete( key, revision );
619 
620         return deleted;
621     }
622 
623 
624     /**
625      * Delete the value from an entry associated with the given key. If the value
626      * If the value is present, it will be deleted first, later if there are no other 
627      * values associated with this key(which can happen when duplicates are enabled), 
628      * we will remove the key from the tree.
629      * 
630      * @param key The key for the entry we try to remove
631      * @param value The value to delete (can be null)
632      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
633      */
634     public Tuple<K, V> delete( K key, V value ) throws IOException
635     {
636         if ( key == null )
637         {
638             throw new IllegalArgumentException( "Key must not be null" );
639         }
640 
641         if ( value == null )
642         {
643             throw new IllegalArgumentException( "Value must not be null" );
644         }
645 
646         long revision = generateRevision();
647 
648         Tuple<K, V> deleted = delete( key, value, revision );
649 
650         return deleted;
651     }
652 
653 
654     /**
655      * Delete the entry which key is given as a parameter. If the entry exists, it will
656      * be removed from the tree, the old tuple will be returned. Otherwise, null is returned.
657      * 
658      * @param key The key for the entry we try to remove
659      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
660      */
661     private Tuple<K, V> delete( K key, long revision ) throws IOException
662     {
663         return delete( key, null, revision );
664     }
665 
666 
667     /**
668      * 
669      * Deletes the given <key,value> pair if both key and value match. If the given value is null
670      * and there is no null value associated with the given key then the entry with the given key
671      * will be removed.
672      *
673      * @param key The key to be removed 
674      * @param value The value to be removed (can be null, and when no null value exists the key will be removed irrespective of the value)
675      * @param revision The revision to be associated with this operation
676      * @return
677      * @throws IOException
678      */
679     private Tuple<K, V> delete( K key, V value, long revision ) throws IOException
680     {
681         writeLock.lock();
682 
683         try
684         {
685             // If the key exists, the existing value will be replaced. We store it
686             // to return it to the caller.
687             Tuple<K, V> tuple = null;
688 
689             // Try to delete the entry starting from the root page. Here, the root
690             // page may be either a Node or a Leaf
691             DeleteResult<K, V> result = rootPage.delete( revision, key, value, null, -1 );
692 
693             if ( result instanceof NotPresentResult )
694             {
695                 // Key not found.
696                 return null;
697             }
698 
699             // Keep the oldRootPage so that we can later access it
700             Page<K, V> oldRootPage = rootPage;
701 
702             if ( result instanceof RemoveResult )
703             {
704                 // The element was found, and removed
705                 RemoveResult<K, V> removeResult = ( RemoveResult<K, V> ) result;
706 
707                 Page<K, V> modifiedPage = removeResult.getModifiedPage();
708 
709                 // Write the modified page on disk
710                 // Note that we don't use the holder, the new root page will
711                 // remain in memory.
712                 ElementHolder<Page<K, V>, K, V> holder = recordManager.writePage( this, modifiedPage,
713                     revision );
714 
715                 // Store the offset on disk in the page in memory
716                 ( ( AbstractPage<K, V> ) modifiedPage ).setOffset( ( ( PageHolder<K, V> ) holder )
717                     .getOffset() );
718 
719                 // Store the last offset on disk in the page in memory
720                 ( ( AbstractPage<K, V> ) modifiedPage )
721                     .setLastOffset( ( ( PageHolder<K, V> ) holder )
722                         .getLastOffset() );
723 
724                 // This is a new root
725                 rootPage = modifiedPage;
726                 tuple = removeResult.getRemovedElement();
727             }
728 
729             // Decrease the number of elements in the current tree if the deletion is successful
730             if ( tuple != null )
731             {
732                 btreeHeader.decrementNbElems();
733 
734                 // We have to update the rootPage on disk
735                 // Update the BTree header now
736                 recordManager.updateBtreeHeader( this, ( ( AbstractPage<K, V> ) rootPage ).getOffset() );
737             }
738 
739             recordManager.addFreePages( this, result.getCopiedPages() );
740 
741             // Store the created rootPage into the revision BTree, this will be stored in RecordManager only if revisions are set to keep
742             recordManager.storeRootPage( this, rootPage );
743 
744             // Return the value we have found if it was modified
745             return tuple;
746         }
747         finally
748         {
749             // See above
750             writeLock.unlock();
751         }
752     }
753 
754 
755     /**
756      * Find a value in the tree, given its key. If the key is not found,
757      * it will throw a KeyNotFoundException. <br/>
758      * Note that we can get a null value stored, or many values.
759      * 
760      * @param key The key we are looking at
761      * @return The found value, or null if the key is not present in the tree
762      * @throws KeyNotFoundException If the key is not found in the BTree
763      * @throws IOException TODO
764      */
765     public V get( K key ) throws IOException, KeyNotFoundException
766     {
767         return rootPage.get( key );
768     }
769 
770 
771     /**
772      * @see Page#getValues(Object)
773      */
774     public ValueCursor<V> getValues( K key ) throws IOException, KeyNotFoundException
775     {
776         return rootPage.getValues( key );
777     }
778 
779 
780     /**
781      * Find a value in the tree, given its key, at a specific revision. If the key is not found,
782      * it will throw a KeyNotFoundException. <br/>
783      * Note that we can get a null value stored, or many values.
784      * 
785      * @param revision The revision for which we want to find a key
786      * @param key The key we are looking at
787      * @return The found value, or null if the key is not present in the tree
788      * @throws KeyNotFoundException If the key is not found in the BTree
789      * @throws IOException If there was an issue while fetching data from the disk
790      */
791     public V get( long revision, K key ) throws IOException, KeyNotFoundException
792     {
793         // Fetch the root page for this revision
794         Page<K, V> revisionRootPage = getRootPage( revision );
795 
796         return revisionRootPage.get( key );
797     }
798 
799 
800     /**
801      * Checks if the given key exists.
802      *  
803      * @param key The key we are looking at
804      * @return true if the key is present, false otherwise
805      * @throws IOException If we have an error while trying to access the page
806      */
807     public boolean hasKey( K key ) throws IOException
808     {
809         if ( key == null )
810         {
811             return false;
812         }
813 
814         return rootPage.hasKey( key );
815     }
816 
817 
818     /**
819      * Checks if the given key exists for a given revision.
820      *  
821      * @param revision The revision for which we want to find a key
822      * @param key The key we are looking at
823      * @return true if the key is present, false otherwise
824      * @throws IOException If we have an error while trying to access the page
825      * @throws KeyNotFoundException If the key is not found in the BTree
826      */
827     public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException
828     {
829         if ( key == null )
830         {
831             return false;
832         }
833 
834         // Fetch the root page for this revision
835         Page<K, V> revisionRootPage = getRootPage( revision );
836 
837         return revisionRootPage.hasKey( key );
838     }
839 
840 
841     /**
842      * Checks if the BTree contains the given key with the given value.
843      * 
844      * @param key The key we are looking for
845      * @param value The value associated with the given key
846      * @return true if the key and value are associated with each other, false otherwise
847      */
848     public boolean contains( K key, V value ) throws IOException
849     {
850         return rootPage.contains( key, value );
851     }
852 
853 
854     /**
855      * Checks if the BTree contains the given key with the given value for a given revision
856      * 
857      * @param revision The revision we would like to browse
858      * @param key The key we are looking for
859      * @param value The value associated with the given key
860      * @return true if the key and value are associated with each other, false otherwise
861      * @throws KeyNotFoundException If the key is not found in the BTree
862      */
863     public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException
864     {
865         // Fetch the root page for this revision
866         Page<K, V> revisionRootPage = getRootPage( revision );
867 
868         return revisionRootPage.contains( key, value );
869     }
870 
871 
872     /**
873      * Creates a cursor starting at the beginning of the tree
874      * 
875      * @return A cursor on the btree
876      * @throws IOException
877      */
878     public TupleCursor<K, V> browse() throws IOException
879     {
880         Transaction<K, V> transaction = beginReadTransaction();
881 
882         // Fetch the root page for this revision
883         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
884 
885         CursorImpl<K, V> cursor = rootPage.browse( transaction, stack );
886 
887         return cursor;
888     }
889 
890 
891     /**
892      * Creates a cursor starting at the beginning of the tree, for a given revision
893      * 
894      * @param revision The revision we would like to browse
895      * @return A cursor on the btree
896      * @throws IOException If we had an issue while fetching data from the disk
897      * @throws KeyNotFoundException If the key is not found in the BTree
898      */
899     public CursorImpl<K, V> browse( long revision ) throws IOException, KeyNotFoundException
900     {
901         Transaction<K, V> transaction = beginReadTransaction();
902 
903         // Fetch the root page for this revision
904         Page<K, V> revisionRootPage = getRootPage( revision );
905 
906         // And get the cursor
907         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
908         CursorImpl<K, V> cursor = revisionRootPage.browse( transaction, stack );
909 
910         return cursor;
911     }
912 
913 
914     /**
915      * Creates a cursor starting on the given key
916      * 
917      * @param key The key which is the starting point. If the key is not found,
918      * then the cursor will always return null.
919      * @return A cursor on the btree
920      * @throws IOException
921      */
922     public CursorImpl<K, V> browseFrom( K key ) throws IOException
923     {
924         Transaction<K, V> transaction = beginReadTransaction();
925 
926         // Fetch the root page for this revision
927         CursorImpl<K, V> cursor = rootPage.browse( key, transaction, new LinkedList<ParentPos<K, V>>() );
928 
929         return cursor;
930     }
931 
932 
933     /**
934      * Creates a cursor starting on the given key at the given revision
935      * 
936      * @param The revision we are looking for
937      * @param key The key which is the starting point. If the key is not found,
938      * then the cursor will always return null.
939      * @return A cursor on the btree
940      * @throws IOException If wxe had an issue reading the BTree from disk
941      * @throws KeyNotFoundException  If we can't find a rootPage for this revision
942      */
943     public CursorImpl<K, V> browseFrom( long revision, K key ) throws IOException, KeyNotFoundException
944     {
945         Transaction<K, V> transaction = beginReadTransaction();
946 
947         // Fetch the rootPage for this revision
948         Page<K, V> revisionRootPage = getRootPage( revision );
949 
950         // And get the cursor
951         LinkedList<ParentPos<K, V>> stack = new LinkedList<ParentPos<K, V>>();
952         CursorImpl<K, V> cursor = revisionRootPage.browse( key, transaction, stack );
953 
954         return cursor;
955     }
956 
957 
958     /**
959      * Insert an entry in the BTree.
960      * <p>
961      * We will replace the value if the provided key already exists in the
962      * btree.
963      * <p>
964      * The revision number is the revision to use to insert the data.
965      *
966      * @param key Inserted key
967      * @param value Inserted value
968      * @param revision The revision to use
969      * @return an instance of the InsertResult.
970      */
971     /*No qualifier*/InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
972     {
973         if ( key == null )
974         {
975             throw new IllegalArgumentException( "Key must not be null" );
976         }
977 
978         // If the key exists, the existing value will be replaced. We store it
979         // to return it to the caller.
980         V modifiedValue = null;
981 
982         // Try to insert the new value in the tree at the right place,
983         // starting from the root page. Here, the root page may be either
984         // a Node or a Leaf
985         InsertResult<K, V> result = rootPage.insert( revision, key, value );
986 
987         if ( result instanceof ModifyResult )
988         {
989             ModifyResult<K, V> modifyResult = ( ( ModifyResult<K, V> ) result );
990 
991             Page<K, V> modifiedPage = modifyResult.getModifiedPage();
992 
993             // Write the modified page on disk
994             // Note that we don't use the holder, the new root page will
995             // remain in memory.
996             ElementHolder<Page<K, V>, K, V> holder = recordManager.writePage( this, modifiedPage,
997                 revision );
998 
999             // The root has just been modified, we haven't split it
1000             // Get it and make it the current root page
1001             rootPage = modifiedPage;
1002 
1003             modifiedValue = modifyResult.getModifiedValue();
1004         }
1005         else
1006         {
1007             // We have split the old root, create a new one containing
1008             // only the pivotal we got back
1009             SplitResult<K, V> splitResult = ( ( SplitResult<K, V> ) result );
1010 
1011             K pivot = splitResult.getPivot();
1012             Page<K, V> leftPage = splitResult.getLeftPage();
1013             Page<K, V> rightPage = splitResult.getRightPage();
1014             Page<K, V> newRootPage = null;
1015 
1016             // If the BTree is managed, we have to write the two pages that were created
1017             // and to keep a track of the two offsets for the upper node
1018             ElementHolder<Page<K, V>, K, V> holderLeft = recordManager.writePage( this,
1019                 leftPage, revision );
1020 
1021             ElementHolder<Page<K, V>, K, V> holderRight = recordManager.writePage( this,
1022                 rightPage, revision );
1023 
1024             // Create the new rootPage
1025             newRootPage = new Node<K, V>( this, revision, pivot, holderLeft, holderRight );
1026 
1027             // If the BTree is managed, we now have to write the page on disk
1028             // and to add this page to the list of modified pages
1029             ElementHolder<Page<K, V>, K, V> holder = recordManager
1030                 .writePage( this, newRootPage, revision );
1031 
1032             rootPage = newRootPage;
1033         }
1034 
1035         // Increase the number of element in the current tree if the insertion is successful
1036         // and does not replace an element
1037         if ( modifiedValue == null )
1038         {
1039             btreeHeader.incrementNbElems();
1040         }
1041 
1042         // If the BTree is managed, we have to update the rootPage on disk
1043         // Update the BTree header now
1044         recordManager.updateBtreeHeader( this, ( ( AbstractPage<K, V> ) rootPage ).getOffset() );
1045 
1046         // Moved the free pages into the list of free pages
1047         recordManager.addFreePages( this, result.getCopiedPages() );
1048 
1049         // Store the created rootPage into the revision BTree, this will be stored in RecordManager only if revisions are set to keep
1050         recordManager.storeRootPage( this, rootPage );
1051 
1052         // Return the value we have found if it was modified
1053         return result;
1054     }
1055 
1056 
1057     /**
1058      * Starts a Read Only transaction. If the transaction is not closed, it will be 
1059      * automatically closed after the timeout
1060      * @return The created transaction
1061      */
1062     private Transaction<K, V> beginReadTransaction()
1063     {
1064         Transaction<K, V> readTransaction = new Transaction<K, V>( rootPage, btreeHeader.getRevision() - 1,
1065             System.currentTimeMillis() );
1066 
1067         readTransactions.add( readTransaction );
1068 
1069         return readTransaction;
1070     }
1071 
1072 
1073     /**
1074      * @return the comparator
1075      */
1076     public Comparator<K> getComparator()
1077     {
1078         return comparator;
1079     }
1080 
1081 
1082     /**
1083      * @param comparator the comparator to set
1084      */
1085     public void setComparator( Comparator<K> comparator )
1086     {
1087         this.comparator = comparator;
1088     }
1089 
1090 
1091     /**
1092      * @param keySerializer the Key serializer to set
1093      */
1094     public void setKeySerializer( ElementSerializer<K> keySerializer )
1095     {
1096         this.keySerializer = keySerializer;
1097         this.comparator = keySerializer.getComparator();
1098         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
1099     }
1100 
1101 
1102     /**
1103      * @param valueSerializer the Value serializer to set
1104      */
1105     public void setValueSerializer( ElementSerializer<V> valueSerializer )
1106     {
1107         this.valueSerializer = valueSerializer;
1108         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
1109     }
1110 
1111 
1112     /**
1113      * Write the data in the ByteBuffer, and eventually on disk if needed.
1114      * 
1115      * @param channel The channel we want to write to
1116      * @param bb The ByteBuffer we want to feed
1117      * @param buffer The data to inject
1118      * @throws IOException If the write failed
1119      */
1120     private void writeBuffer( FileChannel channel, ByteBuffer bb, byte[] buffer ) throws IOException
1121     {
1122         int size = buffer.length;
1123         int pos = 0;
1124 
1125         // Loop until we have written all the data
1126         do
1127         {
1128             if ( bb.remaining() >= size )
1129             {
1130                 // No flush, as the ByteBuffer is big enough
1131                 bb.put( buffer, pos, size );
1132                 size = 0;
1133             }
1134             else
1135             {
1136                 // Flush the data on disk, reinitialize the ByteBuffer
1137                 int len = bb.remaining();
1138                 size -= len;
1139                 bb.put( buffer, pos, len );
1140                 pos += len;
1141 
1142                 bb.flip();
1143 
1144                 channel.write( bb );
1145 
1146                 bb.clear();
1147             }
1148         }
1149         while ( size > 0 );
1150     }
1151 
1152 
1153     /**
1154      * Get the rootPzge associated to a give revision.
1155      * 
1156      * @param revision The revision we are looking for
1157      * @return The rootPage associated to this revision
1158      * @throws IOException If we had an issue while accessing the underlying file
1159      * @throws KeyNotFoundException If the revision does not exist for this Btree
1160      */
1161     private Page<K, V> getRootPage( long revision ) throws IOException, KeyNotFoundException
1162     {
1163         return recordManager.getRootPage( this, revision );
1164     }
1165 
1166 
1167     /**
1168      * Flush the latest revision to disk. We will replace the current file by the new one, as
1169      * we flush in a temporary file.
1170      */
1171     public void flush() throws IOException
1172     {
1173     }
1174 
1175 
1176     /**
1177      * @return the readTimeOut
1178      */
1179     public long getReadTimeOut()
1180     {
1181         return readTimeOut;
1182     }
1183 
1184 
1185     /**
1186      * @param readTimeOut the readTimeOut to set
1187      */
1188     public void setReadTimeOut( long readTimeOut )
1189     {
1190         this.readTimeOut = readTimeOut;
1191     }
1192 
1193 
1194     /**
1195      * @return the name
1196      */
1197     public String getName()
1198     {
1199         return btreeHeader.getName();
1200     }
1201 
1202 
1203     /**
1204      * @param name the name to set
1205      */
1206     public void setName( String name )
1207     {
1208         btreeHeader.setName( name );
1209     }
1210 
1211 
1212     /**
1213      * @return the writeBufferSize
1214      */
1215     public int getWriteBufferSize()
1216     {
1217         return writeBufferSize;
1218     }
1219 
1220 
1221     /**
1222      * @param writeBufferSize the writeBufferSize to set
1223      */
1224     public void setWriteBufferSize( int writeBufferSize )
1225     {
1226         this.writeBufferSize = writeBufferSize;
1227     }
1228 
1229 
1230     /**
1231      * Create a ValueHolder depending on the kind of holder we want.
1232      * 
1233      * @param value The value to store
1234      * @return The value holder
1235      */
1236     @SuppressWarnings("unchecked")
1237     /* no qualifier */ValueHolder<V> createValueHolder( V value )
1238     {
1239         return new ValueHolder<V>( recordManager, valueSerializer, value );
1240     }
1241 
1242 
1243     /**
1244      * Create a ValueHolder depending on the kind of holder we want.
1245      * 
1246      * @param value The value to store
1247      * @return The value holder
1248      */
1249     /* no qualifier */ElementHolder<Page<K, V>, K, V> createPageHolder( Page<K, V> value )
1250     {
1251         return new PageHolder<K, V>( this, value,
1252             value.getOffset(), value.getLastOffset() );
1253     }
1254 
1255 
1256     /**
1257      * @return the keySerializer
1258      */
1259     public ElementSerializer<K> getKeySerializer()
1260     {
1261         return keySerializer;
1262     }
1263 
1264 
1265     /**
1266      * @return the keySerializer FQCN
1267      */
1268     public String getKeySerializerFQCN()
1269     {
1270         return btreeHeader.getKeySerializerFQCN();
1271     }
1272 
1273 
1274     /**
1275      * @return the valueSerializer
1276      */
1277     public ElementSerializer<V> getValueSerializer()
1278     {
1279         return valueSerializer;
1280     }
1281 
1282 
1283     /**
1284      * @return the valueSerializer FQCN
1285      */
1286     public String getValueSerializerFQCN()
1287     {
1288         return btreeHeader.getValueSerializerFQCN();
1289     }
1290 
1291 
1292     /** 
1293      * @return The current BTree revision
1294      */
1295     public long getRevision()
1296     {
1297         return btreeHeader.getRevision();
1298     }
1299 
1300 
1301     /**
1302      * @param revision the revision to set
1303      */
1304     /* No qualifier */void setRevision( long revision )
1305     {
1306         btreeHeader.setRevision( revision );
1307     }
1308 
1309 
1310     /** 
1311      * @return The current number of elements in the BTree
1312      */
1313     public long getNbElems()
1314     {
1315         return btreeHeader.getNbElems();
1316     }
1317 
1318 
1319     /**
1320      * @param nbElems the nbElems to set
1321      */
1322     /* No qualifier */void setNbElems( long nbElems )
1323     {
1324         btreeHeader.setNbElems( nbElems );
1325     }
1326 
1327 
1328     /**
1329      * @return true if this BTree allow duplicate values
1330      */
1331     public boolean isAllowDuplicates()
1332     {
1333         return btreeHeader.isAllowDuplicates();
1334     }
1335 
1336 
1337     /* No qualifier */void setAllowDuplicates( boolean allowDuplicates )
1338     {
1339         btreeHeader.setAllowDuplicates( allowDuplicates );
1340     }
1341 
1342 
1343     /**
1344      * @see Object#toString()
1345      */
1346     public String toString()
1347     {
1348         StringBuilder sb = new StringBuilder();
1349 
1350         sb.append( "Managed BTree" );
1351         sb.append( "[" ).append( btreeHeader.getName() ).append( "]" );
1352         sb.append( "( pageSize:" ).append( btreeHeader.getPageSize() );
1353 
1354         if ( rootPage != null )
1355         {
1356             sb.append( ", nbEntries:" ).append( btreeHeader.getNbElems() );
1357         }
1358         else
1359         {
1360             sb.append( ", nbEntries:" ).append( 0 );
1361         }
1362 
1363         sb.append( ", comparator:" );
1364 
1365         if ( comparator == null )
1366         {
1367             sb.append( "null" );
1368         }
1369         else
1370         {
1371             sb.append( comparator.getClass().getSimpleName() );
1372         }
1373 
1374         sb.append( ", DuplicatesAllowed: " ).append( btreeHeader.isAllowDuplicates() );
1375 
1376         sb.append( ") : \n" );
1377         sb.append( rootPage.dumpPage( "" ) );
1378 
1379         return sb.toString();
1380     }
1381 }