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;
21  
22  
23  import java.io.IOException;
24  import java.lang.reflect.Array;
25  import java.util.Comparator;
26  import java.util.concurrent.ConcurrentLinkedQueue;
27  import java.util.concurrent.locks.ReentrantLock;
28  
29  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
30  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
31  
32  
33  /**
34   * A BTree abstract class containing the methods shared by the PersistedBTree or the InMemoryBTree
35   * implementations.
36   *
37   * @param <K> The Key type
38   * @param <V> The Value type
39   *
40   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
41   */
42  /* No qualifier*/abstract class AbstractBTree<K, V> implements BTree<K, V>
43  {
44      /** The read transaction timeout */
45      protected long readTimeOut = DEFAULT_READ_TIMEOUT;
46  
47      /** The Header for a managed BTree */
48      protected BTreeHeader btreeHeader;
49  
50      /** The current rootPage */
51      protected volatile Page<K, V> rootPage;
52  
53      /** The Key serializer used for this tree.*/
54      protected ElementSerializer<K> keySerializer;
55  
56      /** The Value serializer used for this tree. */
57      protected ElementSerializer<V> valueSerializer;
58  
59      /** The list of read transactions being executed */
60      protected ConcurrentLinkedQueue<ReadTransaction<K, V>> readTransactions;
61  
62      /** The size of the buffer used to write data in disk */
63      protected int writeBufferSize;
64  
65      /** A lock used to protect the write operation against concurrent access */
66      protected ReentrantLock writeLock;
67  
68      /** Flag to enable duplicate key support */
69      private boolean allowDuplicates;
70  
71      /** The thread responsible for the cleanup of timed out reads */
72      protected Thread readTransactionsThread;
73  
74      /** The BTree type : either in-memory, disk backed or persisted */
75      private BTreeTypeEnum type;
76  
77      /** The current transaction */
78      protected WriteTransaction writeTransaction;
79  
80  
81      /**
82       * Starts a Read Only transaction. If the transaction is not closed, it will be
83       * automatically closed after the timeout
84       *
85       * @return The created transaction
86       */
87      protected ReadTransaction<K, V> beginReadTransaction()
88      {
89          ReadTransaction<K, V> readTransaction = new ReadTransaction<K, V>( rootPage, btreeHeader.getRevision() - 1,
90              System.currentTimeMillis() );
91  
92          readTransactions.add( readTransaction );
93  
94          return readTransaction;
95      }
96  
97  
98      /**
99       * {@inheritDoc}
100      */
101     public TupleCursor<K, V> browse() throws IOException
102     {
103         ReadTransaction<K, V> transaction = beginReadTransaction();
104 
105         // Fetch the root page for this revision
106         ParentPos<K, V>[] stack = (ParentPos<K, V>[]) Array.newInstance( ParentPos.class, 32 );
107 
108         TupleCursor<K, V> cursor = rootPage.browse( transaction, stack, 0 );
109 
110         // Set the position before the first element
111         cursor.beforeFirst();
112 
113         return cursor;
114     }
115 
116 
117     /**
118      * {@inheritDoc}
119      */
120     public TupleCursor<K, V> browse( long revision ) throws IOException, KeyNotFoundException
121     {
122         ReadTransaction<K, V> transaction = beginReadTransaction();
123 
124         // Fetch the root page for this revision
125         Page<K, V> revisionRootPage = getRootPage( revision );
126 
127         ParentPos<K, V>[] stack = (ParentPos<K, V>[]) Array.newInstance( ParentPos.class, 32 );
128 
129         // And get the cursor
130         TupleCursor<K, V> cursor = revisionRootPage.browse( transaction, stack, 0 );
131 
132         return cursor;
133     }
134 
135 
136     /**
137      * {@inheritDoc}
138      */
139     public TupleCursor<K, V> browseFrom( K key ) throws IOException
140     {
141         ReadTransaction<K, V> transaction = beginReadTransaction();
142 
143         // Fetch the root page for this revision
144         ParentPos<K, V>[] stack = (ParentPos<K, V>[]) Array.newInstance( ParentPos.class, 32 );
145 
146         TupleCursor<K, V> cursor = rootPage.browse( key, transaction, stack, 0 );
147 
148         return cursor;
149     }
150 
151 
152     /**
153      * {@inheritDoc}
154      */
155     public TupleCursor<K, V> browseFrom( long revision, K key ) throws IOException, KeyNotFoundException
156     {
157         ReadTransaction<K, V> transaction = beginReadTransaction();
158 
159         // Fetch the rootPage for this revision
160         Page<K, V> revisionRootPage = getRootPage( revision );
161 
162         ParentPos<K, V>[] stack = (ParentPos<K, V>[]) Array.newInstance( ParentPos.class, 32 );
163 
164         // And get the cursor
165         TupleCursor<K, V> cursor = revisionRootPage.browse( key, transaction, stack, 0 );
166 
167         return cursor;
168     }
169 
170 
171     /**
172      * {@inheritDoc}
173      */
174     public boolean contains( K key, V value ) throws IOException
175     {
176         return rootPage.contains( key, value );
177     }
178 
179 
180     /**
181      * {@inheritDoc}
182      */
183     public boolean contains( long revision, K key, V value ) throws IOException, KeyNotFoundException
184     {
185         // Fetch the root page for this revision
186         Page<K, V> revisionRootPage = getRootPage( revision );
187 
188         return revisionRootPage.contains( key, value );
189     }
190 
191 
192     /**
193      * {@inheritDoc}
194      */
195     public Tuple<K, V> delete( K key ) throws IOException
196     {
197         if ( key == null )
198         {
199             throw new IllegalArgumentException( "Key must not be null" );
200         }
201 
202         long revision = generateRevision();
203 
204         Tuple<K, V> deleted = delete( key, revision );
205 
206         return deleted;
207     }
208 
209 
210     /**
211      * {@inheritDoc}
212      */
213     public Tuple<K, V> delete( K key, V value ) throws IOException
214     {
215         if ( key == null )
216         {
217             throw new IllegalArgumentException( "Key must not be null" );
218         }
219 
220         if ( value == null )
221         {
222             throw new IllegalArgumentException( "Value must not be null" );
223         }
224 
225         long revision = generateRevision();
226 
227         Tuple<K, V> deleted = delete( key, value, revision );
228 
229         return deleted;
230     }
231 
232 
233     /**
234      * Delete the entry which key is given as a parameter. If the entry exists, it will
235      * be removed from the tree, the old tuple will be returned. Otherwise, null is returned.
236      *
237      * @param key The key for the entry we try to remove
238      * @return A Tuple<K, V> containing the removed entry, or null if it's not found.
239      */
240     /*no qualifier*/Tuple<K, V> delete( K key, long revision ) throws IOException
241     {
242         return delete( key, null, revision );
243     }
244 
245 
246     /*no qualifier*/abstract Tuple<K, V> delete( K key, V value, long revision ) throws IOException;
247 
248 
249     /**
250      * {@inheritDoc}
251      */
252     public V insert( K key, V value ) throws IOException
253     {
254         long revision = generateRevision();
255 
256         V existingValue = null;
257 
258         try
259         {
260             if ( writeTransaction == null )
261             {
262                 writeLock.lock();
263             }
264 
265             InsertResult<K, V> result = insert( key, value, revision );
266 
267             if ( result instanceof ModifyResult )
268             {
269                 existingValue = ( ( ModifyResult<K, V> ) result ).getModifiedValue();
270             }
271         }
272         finally
273         {
274             // See above
275             if ( writeTransaction == null )
276             {
277                 writeLock.unlock();
278             }
279         }
280 
281         return existingValue;
282     }
283 
284 
285     /**
286      * {@inheritDoc}
287      */
288     /* no qualifier */abstract InsertResult<K, V> insert( K key, V value, long revision ) throws IOException;
289 
290 
291     /**
292      * Flush the latest revision to disk. We will replace the current file by the new one, as
293      * we flush in a temporary file.
294      */
295     public void flush() throws IOException
296     {
297     }
298 
299 
300     /**
301      * {@inheritDoc}
302      */
303     public V get( K key ) throws IOException, KeyNotFoundException
304     {
305         return rootPage.get( key );
306     }
307 
308 
309     /**
310      * {@inheritDoc}
311      */
312     public V get( long revision, K key ) throws IOException, KeyNotFoundException
313     {
314         // Fetch the root page for this revision
315         Page<K, V> revisionRootPage = getRootPage( revision );
316 
317         return revisionRootPage.get( key );
318     }
319 
320 
321     /**
322      * {@inheritDoc}
323      */
324     public Page<K, V> getRootPage()
325     {
326         return rootPage;
327     }
328 
329 
330     /**
331      * {@inheritDoc}
332      */
333     /* no qualifier */void setRootPage( Page<K, V> root )
334     {
335         rootPage = root;
336     }
337 
338 
339     /**
340      * {@inheritDoc}
341      */
342     public ValueCursor<V> getValues( K key ) throws IOException, KeyNotFoundException
343     {
344         return rootPage.getValues( key );
345     }
346 
347 
348     /**
349      * {@inheritDoc}
350      */
351     public boolean hasKey( K key ) throws IOException
352     {
353         if ( key == null )
354         {
355             return false;
356         }
357 
358         return rootPage.hasKey( key );
359     }
360 
361 
362     /**
363      * {@inheritDoc}
364      */
365     public boolean hasKey( long revision, K key ) throws IOException, KeyNotFoundException
366     {
367         if ( key == null )
368         {
369             return false;
370         }
371 
372         // Fetch the root page for this revision
373         Page<K, V> revisionRootPage = getRootPage( revision );
374 
375         return revisionRootPage.hasKey( key );
376     }
377 
378 
379     /**
380      * {@inheritDoc}
381      */
382     public ElementSerializer<K> getKeySerializer()
383     {
384         return keySerializer;
385     }
386 
387 
388     /**
389      * {@inheritDoc}
390      */
391     public void setKeySerializer( ElementSerializer<K> keySerializer )
392     {
393         this.keySerializer = keySerializer;
394         btreeHeader.setKeySerializerFQCN( keySerializer.getClass().getName() );
395     }
396 
397 
398     /**
399      * {@inheritDoc}
400      */
401     public String getKeySerializerFQCN()
402     {
403         return btreeHeader.getKeySerializerFQCN();
404     }
405 
406 
407     /**
408      * {@inheritDoc}
409      */
410     public ElementSerializer<V> getValueSerializer()
411     {
412         return valueSerializer;
413     }
414 
415 
416     /**
417      * {@inheritDoc}
418      */
419     public void setValueSerializer( ElementSerializer<V> valueSerializer )
420     {
421         this.valueSerializer = valueSerializer;
422         btreeHeader.setValueSerializerFQCN( valueSerializer.getClass().getName() );
423     }
424 
425 
426     /**
427      * {@inheritDoc}
428      */
429     public String getValueSerializerFQCN()
430     {
431         return btreeHeader.getValueSerializerFQCN();
432     }
433 
434 
435     /**
436      * {@inheritDoc}
437      */
438     public long getRevision()
439     {
440         return btreeHeader.getRevision();
441     }
442 
443 
444     /**
445      * {@inheritDoc}
446      */
447     /* no qualifier */void setRevision( long revision )
448     {
449         btreeHeader.setRevision( revision );
450     }
451 
452 
453     /**
454      * Generates a new revision number. It's only used by the Page instances.
455      *
456      * @return a new incremental revision number
457      */
458     /* no qualifier */long generateRevision()
459     {
460         return btreeHeader.incrementRevision();
461     }
462 
463 
464     /**
465      * {@inheritDoc}
466      */
467     public long getReadTimeOut()
468     {
469         return readTimeOut;
470     }
471 
472 
473     /**
474      * {@inheritDoc}
475      */
476     public void setReadTimeOut( long readTimeOut )
477     {
478         this.readTimeOut = readTimeOut;
479     }
480 
481 
482     /**
483      * {@inheritDoc}
484      */
485     public long getNbElems()
486     {
487         return btreeHeader.getNbElems();
488     }
489 
490 
491     /**
492      * {@inheritDoc}
493      */
494     /* no qualifier */void setNbElems( long nbElems )
495     {
496         btreeHeader.setNbElems( nbElems );
497     }
498 
499 
500     /**
501      * {@inheritDoc}
502      */
503     public int getPageSize()
504     {
505         return btreeHeader.getPageSize();
506     }
507 
508 
509     /**
510      * {@inheritDoc}
511      */
512     public void setPageSize( int pageSize )
513     {
514         if ( pageSize <= 2 )
515         {
516             btreeHeader.setPageSize( DEFAULT_PAGE_SIZE );
517         }
518         else
519         {
520             btreeHeader.setPageSize( getPowerOf2( pageSize ) );
521         }
522     }
523 
524 
525     /**
526      * {@inheritDoc}
527      */
528     public String getName()
529     {
530         return btreeHeader.getName();
531     }
532 
533 
534     /**
535      * {@inheritDoc}
536      */
537     public void setName( String name )
538     {
539         btreeHeader.setName( name );
540     }
541 
542 
543     /**
544      * {@inheritDoc}
545      */
546     public Comparator<K> getComparator()
547     {
548         return keySerializer.getComparator();
549     }
550 
551 
552     /**
553      * {@inheritDoc}
554      */
555     public int getWriteBufferSize()
556     {
557         return writeBufferSize;
558     }
559 
560 
561     /**
562      * {@inheritDoc}
563      */
564     public void setWriteBufferSize( int writeBufferSize )
565     {
566         this.writeBufferSize = writeBufferSize;
567     }
568 
569 
570     /**
571      * {@inheritDoc}
572      */
573     public boolean isAllowDuplicates()
574     {
575         return btreeHeader.isAllowDuplicates();
576     }
577 
578 
579     /**
580      * {@inheritDoc}
581      */
582     public void setAllowDuplicates( boolean allowDuplicates )
583     {
584         btreeHeader.setAllowDuplicates( allowDuplicates );
585     }
586 
587 
588     /**
589      * {@inheritDoc}
590      */
591     public BTreeTypeEnum getType()
592     {
593         return type;
594     }
595 
596 
597     /**
598      * @param type the type to set
599      */
600     public void setType( BTreeTypeEnum type )
601     {
602         this.type = type;
603     }
604 
605 
606     /**
607      * Gets the number which is a power of 2 immediately above the given positive number.
608      */
609     private int getPowerOf2( int size )
610     {
611         int newSize = --size;
612         newSize |= newSize >> 1;
613         newSize |= newSize >> 2;
614         newSize |= newSize >> 4;
615         newSize |= newSize >> 8;
616         newSize |= newSize >> 16;
617         newSize++;
618 
619         return newSize;
620     }
621 
622 
623     /**
624      * Create a thread that is responsible of cleaning the transactions when
625      * they hit the timeout
626      */
627     /*no qualifier*/void createTransactionManager()
628     {
629         Runnable readTransactionTask = new Runnable()
630         {
631             public void run()
632             {
633                 try
634                 {
635                     ReadTransaction<K, V> transaction = null;
636 
637                     while ( !Thread.currentThread().isInterrupted() )
638                     {
639                         long timeoutDate = System.currentTimeMillis() - readTimeOut;
640                         long t0 = System.currentTimeMillis();
641                         int nbTxns = 0;
642 
643                         // Loop on all the transactions from the queue
644                         while ( ( transaction = readTransactions.peek() ) != null )
645                         {
646                             nbTxns++;
647 
648                             if ( transaction.isClosed() )
649                             {
650                                 // The transaction is already closed, remove it from the queue
651                                 readTransactions.poll();
652                                 continue;
653                             }
654 
655                             // Check if the transaction has timed out
656                             if ( transaction.getCreationDate() < timeoutDate )
657                             {
658                                 transaction.close();
659                                 readTransactions.poll();
660                                 continue;
661                             }
662 
663                             // We need to stop now
664                             break;
665                         }
666 
667                         long t1 = System.currentTimeMillis();
668 
669                         if ( nbTxns > 0 )
670                         {
671                             System.out.println( "Processing old txn : " + nbTxns + ", " + ( t1 - t0 ) + "ms" );
672                         }
673 
674                         // Wait until we reach the timeout
675                         Thread.sleep( readTimeOut );
676                     }
677                 }
678                 catch ( InterruptedException ie )
679                 {
680                     //System.out.println( "Interrupted" );
681                 }
682                 catch ( Exception e )
683                 {
684                     throw new RuntimeException( e );
685                 }
686             }
687         };
688 
689         readTransactionsThread = new Thread( readTransactionTask );
690         readTransactionsThread.setDaemon( true );
691         readTransactionsThread.start();
692     }
693 }