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.IOException;
024
025import org.apache.directory.api.ldap.model.cursor.Cursor;
026import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
027import org.apache.directory.api.ldap.model.cursor.SingletonCursor;
028import org.apache.directory.api.ldap.model.cursor.Tuple;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapOtherException;
031import org.apache.directory.api.ldap.model.schema.SchemaManager;
032import org.apache.directory.mavibot.btree.BTree;
033import org.apache.directory.mavibot.btree.BTreeFactory;
034import org.apache.directory.mavibot.btree.RecordManager;
035import org.apache.directory.mavibot.btree.TupleCursor;
036import org.apache.directory.mavibot.btree.ValueCursor;
037import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException;
038import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
039import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
040import org.apache.directory.server.core.api.partition.PartitionTxn;
041import org.apache.directory.server.core.avltree.ArrayMarshaller;
042import org.apache.directory.server.core.avltree.ArrayTree;
043import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
044import org.apache.directory.server.i18n.I18n;
045import org.apache.directory.server.xdbm.AbstractTable;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049
050/**
051 * A Mavibot Table. It extends the default Apache DS Table, when Mavibot is the
052 * underlying database.
053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054 */
055public class MavibotTable<K, V> extends AbstractTable<K, V>
056{
057    /** the underlying B-tree */
058    private BTree<K, V> bt;
059
060    /** The marshaller that will be used to read the values when we have more than one */
061    private ArrayMarshaller<V> arrayMarshaller;
062
063    /** A logger for this class */
064    private static final Logger LOG = LoggerFactory.getLogger( MavibotTable.class );
065
066    /** The used recordManager */
067    protected RecordManager recordMan;
068
069
070    /**
071     * Creates a new instance of MavibotTable.
072     *
073     * @param recordMan The associated RecordManager
074     * @param schemaManager The SchemaManager
075     * @param name The Table name
076     * @param keySerializer The Key serializer
077     * @param valueSerializer The Value serializer
078     * @param allowDuplicates If the table allows duplicate values
079     * @throws IOException If the instance creation failed
080     */
081    public MavibotTable( RecordManager recordMan, SchemaManager schemaManager, String name,
082        ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer, boolean allowDuplicates )
083        throws IOException
084    {
085        this( recordMan, schemaManager, name, keySerializer, valueSerializer, allowDuplicates,
086            AbstractBTreePartition.DEFAULT_CACHE_SIZE );
087    }
088
089
090    /**
091     * Creates a new instance of MavibotTable.
092     *
093     * @param recordMan The associated RecordManager
094     * @param schemaManager The SchemaManager
095     * @param name The Table name
096     * @param keySerializer The Key serializer
097     * @param valueSerializer The Value serializer
098     * @param allowDuplicates If the table allows duplicate values
099     * @param cacheSize The cache size to use
100     * @throws IOException If the instance creation failed
101     */
102    public MavibotTable( RecordManager recordMan, SchemaManager schemaManager, String name,
103        ElementSerializer<K> keySerializer, ElementSerializer<V> valueSerializer, boolean allowDuplicates, int cacheSize )
104        throws IOException
105    {
106        super( schemaManager, name, keySerializer.getComparator(), valueSerializer.getComparator() );
107        this.recordMan = recordMan;
108
109        bt = recordMan.getManagedTree( name );
110
111        if ( bt == null )
112        {
113            bt = BTreeFactory.createPersistedBTree( name, keySerializer, valueSerializer, allowDuplicates, cacheSize );
114
115            try
116            {
117                recordMan.manage( bt );
118            }
119            catch ( BTreeAlreadyManagedException e )
120            {
121                // should never happen
122                throw new RuntimeException( e );
123            }
124        }
125        else
126        {
127            // it is important to set the serializers cause serializers will contain default
128            // comparators when loaded from disk and we need schema aware comparators in certain indices
129            bt.setKeySerializer( keySerializer );
130            bt.setValueSerializer( valueSerializer );
131        }
132
133        this.allowsDuplicates = allowDuplicates;
134        arrayMarshaller = new ArrayMarshaller<>( valueComparator );
135
136        // Initialize the count
137        count = bt.getNbElems();
138    }
139
140
141    /**
142     * {@inheritDoc}
143     */
144    @Override
145    public boolean has( PartitionTxn partitionTxn, K key ) throws LdapException
146    {
147        try
148        {
149            return bt.hasKey( key );
150        }
151        catch ( IOException ioe )
152        {
153            throw new LdapException( ioe );
154        }
155        catch ( KeyNotFoundException knfe )
156        {
157            throw new LdapException( knfe );
158        }
159    }
160
161
162    /**
163     * {@inheritDoc}
164     */
165    @Override
166    public boolean has( PartitionTxn transaction, K key, V value ) throws LdapException
167    {
168        try
169        {
170            return bt.contains( key, value );
171        }
172        catch ( IOException e )
173        {
174            throw new LdapException( e );
175        }
176    }
177
178
179    /**
180     * {@inheritDoc}
181     */
182    @Override
183    public boolean hasGreaterOrEqual( PartitionTxn transaction, K key ) throws LdapException
184    {
185        TupleCursor<K, V> cursor = null;
186
187        try
188        {
189            cursor = bt.browseFrom( key );
190
191            return cursor.hasNext();
192        }
193        catch ( IOException ioe )
194        {
195            throw new LdapOtherException( ioe.getMessage() );
196        }
197        finally
198        {
199            if ( cursor != null )
200            {
201                cursor.close();
202            }
203        }
204    }
205
206
207    /**
208     * {@inheritDoc}
209     */
210    @Override
211    public boolean hasLessOrEqual( PartitionTxn transaction, K key ) throws LdapException
212    {
213        TupleCursor<K, V> cursor = null;
214
215        try
216        {
217            cursor = bt.browseFrom( key );
218
219            org.apache.directory.mavibot.btree.Tuple<K, V> tuple = null;
220
221            if ( cursor.hasNext() )
222            {
223                tuple = cursor.next();
224            }
225
226            // Test for equality first since it satisfies both greater/less than
227            if ( null != tuple && keyComparator.compare( tuple.getKey(), key ) == 0 )
228            {
229                return true;
230            }
231
232            if ( null == tuple )
233            {
234                return count > 0;
235            }
236            else
237            {
238                if ( cursor.hasPrev() )
239                {
240                    return true;
241                }
242            }
243
244            return false;
245        }
246        catch ( Exception e )
247        {
248            throw new LdapException( e );
249        }
250        finally
251        {
252            if ( cursor != null )
253            {
254                cursor.close();
255            }
256        }
257    }
258
259
260    /**
261     * {@inheritDoc}
262     */
263    @Override
264    public boolean hasGreaterOrEqual( PartitionTxn transaction, K key, V val ) throws LdapException
265    {
266        if ( key == null )
267        {
268            return false;
269        }
270
271        if ( !allowsDuplicates )
272        {
273            throw new UnsupportedOperationException( I18n.err( I18n.ERR_593 ) );
274        }
275
276        ValueCursor<V> valueCursor = null;
277
278        try
279        {
280            if ( !bt.hasKey( key ) )
281            {
282                return false;
283            }
284
285            valueCursor = bt.getValues( key );
286
287            int equal = bt.getValueSerializer().compare( val, valueCursor.next() );
288
289            return ( equal >= 0 );
290        }
291        catch ( KeyNotFoundException | IOException e )
292        {
293            throw new LdapException( e.getMessage() );
294        }
295        finally
296        {
297            if ( valueCursor != null )
298            {
299                valueCursor.close();
300            }
301        }
302    }
303
304
305    /**
306     * {@inheritDoc}
307     */
308    @Override
309    public boolean hasLessOrEqual( PartitionTxn partitionTxn, K key, V val ) throws LdapException
310    {
311        if ( key == null )
312        {
313            return false;
314        }
315
316        if ( !allowsDuplicates )
317        {
318            throw new UnsupportedOperationException( I18n.err( I18n.ERR_593 ) );
319        }
320
321        try
322        {
323            if ( !bt.hasKey( key ) )
324            {
325                return false;
326            }
327    
328            ValueCursor<V> dupHolder = bt.getValues( key );
329    
330            return dupHolder.hasNext();
331        }
332        catch ( KeyNotFoundException knfe )
333        {
334            throw new LdapOtherException( knfe.getMessage(), knfe );
335        }
336        catch ( IOException ioe )
337        {
338            throw new LdapOtherException( ioe.getMessage(), ioe );
339        }
340    }
341
342
343    /**
344     * {@inheritDoc}
345     */
346    @Override
347    public V get( PartitionTxn transaction, K key ) throws LdapException
348    {
349        if ( key == null )
350        {
351            return null;
352        }
353
354        try
355        {
356            return bt.get( key );
357        }
358        catch ( KeyNotFoundException knfe )
359        {
360            return null;
361        }
362        catch ( Exception e )
363        {
364            throw new LdapException( e );
365        }
366    }
367
368
369    /**
370     * {@inheritDoc}
371     */
372    @Override
373    public void put( PartitionTxn partitionTxn, K key, V value ) throws LdapException
374    {
375        try
376        {
377            if ( ( value == null ) || ( key == null ) )
378            {
379                throw new IllegalArgumentException( I18n.err( I18n.ERR_594 ) );
380            }
381
382            V existingVal = bt.insert( key, value );
383
384            if ( existingVal == null )
385            {
386                count++;
387            }
388        }
389        catch ( IOException ioe )
390        {
391            LOG.error( I18n.err( I18n.ERR_131, key, name ), ioe );
392            throw new LdapOtherException( ioe.getMessage(), ioe );
393        }
394    }
395
396
397    /**
398     * {@inheritDoc}
399     */
400    @Override
401    public void remove( PartitionTxn partitionTxn, K key ) throws LdapException
402    {
403        try
404        {
405            if ( key == null )
406            {
407                return;
408            }
409
410            // Get the associated valueHolder
411            if ( bt.isAllowDuplicates() )
412            {
413                ValueCursor<V> valueCursor = bt.getValues( key );
414                int size = valueCursor.size();
415                valueCursor.close();
416                org.apache.directory.mavibot.btree.Tuple<K, V> returned = bt.delete( key );
417
418                if ( null == returned )
419                {
420                    return;
421                }
422
423                count -= size;
424            }
425            else
426            {
427                org.apache.directory.mavibot.btree.Tuple<K, V> returned = bt.delete( key );
428
429                if ( null == returned )
430                {
431                    return;
432                }
433
434                count--;
435            }
436        }
437        catch ( IOException | KeyNotFoundException e )
438        {
439            LOG.error( I18n.err( I18n.ERR_133, key, name ), e );
440
441            throw new LdapOtherException( e.getMessage(), e );
442        }
443    }
444
445
446    /**
447     * {@inheritDoc}
448     */
449    @Override
450    public void remove( PartitionTxn partitionTxn, K key, V value ) throws LdapException
451    {
452        try
453        {
454            if ( key == null )
455            {
456                return;
457            }
458
459            org.apache.directory.mavibot.btree.Tuple<K, V> tuple = bt.delete( key, value );
460
461            // We decrement the counter only when the key was found
462            if ( tuple != null )
463            {
464                count--;
465            }
466        }
467        catch ( Exception e )
468        {
469            LOG.error( I18n.err( I18n.ERR_132, key, value, name ), e );
470        }
471    }
472
473
474    /**
475     * {@inheritDoc}
476     */
477    @Override
478    public Cursor<Tuple<K, V>> cursor()
479    {
480        return new MavibotCursor<>( this );
481    }
482
483
484    /**
485     * {@inheritDoc}
486     */
487    @Override
488    public Cursor<Tuple<K, V>> cursor( PartitionTxn partitionTxn, K key ) throws LdapException
489    {
490        if ( key == null )
491        {
492            return new EmptyCursor<>();
493        }
494
495        try
496        {
497            if ( !allowsDuplicates )
498            {
499                V val = bt.get( key );
500
501                return new SingletonCursor<>( new Tuple<K, V>( key, val ) );
502            }
503            else
504            {
505                ValueCursor<V> dupHolder = bt.getValues( key );
506
507                return new KeyTupleValueCursor<>( dupHolder, key );
508            }
509        }
510        catch ( KeyNotFoundException knfe )
511        {
512            return new EmptyCursor<>();
513        }
514        catch ( Exception e )
515        {
516            throw new LdapException( e );
517        }
518    }
519
520
521    /**
522     * {@inheritDoc}
523     */
524    @Override
525    public Cursor<V> valueCursor( PartitionTxn transaction, K key ) throws LdapException
526    {
527        if ( key == null )
528        {
529            return new EmptyCursor<>();
530        }
531
532        try
533        {
534            if ( !allowsDuplicates )
535            {
536                V val = bt.get( key );
537
538                return new SingletonCursor<>( val );
539            }
540            else
541            {
542                ValueCursor<V> dupCursor = bt.getValues( key );
543
544                return new ValueTreeCursor<>( dupCursor );
545            }
546        }
547        catch ( KeyNotFoundException knfe )
548        {
549            return new EmptyCursor<>();
550        }
551        catch ( Exception e )
552        {
553            throw new LdapException( e );
554        }
555    }
556    
557    
558    /**
559     * {@inheritDoc}
560     */
561    @Override
562    public synchronized void close( PartitionTxn transaction ) throws LdapException
563    {
564        try
565        {
566            sync();
567        }
568        catch  ( IOException ioe )
569        {
570            throw new LdapOtherException( ioe.getMessage() );
571        }
572    }
573
574
575    /**
576     * {@inheritDoc}
577     */
578    @Override
579    public long count( PartitionTxn transaction, K key ) throws LdapException
580    {
581        if ( key == null )
582        {
583            return 0;
584        }
585
586        try
587        {
588            if ( bt.isAllowDuplicates() )
589            {
590                ValueCursor<V> dupHolder = bt.getValues( key );
591                int size = dupHolder.size();
592                dupHolder.close();
593
594                return size;
595            }
596            else
597            {
598                if ( bt.hasKey( key ) )
599                {
600                    return 1;
601                }
602                else
603                {
604                    return 0;
605                }
606            }
607        }
608        catch ( KeyNotFoundException knfe )
609        {
610            // No key
611            return 0;
612        }
613        catch ( IOException ioe )
614        {
615            throw new LdapOtherException( ioe.getMessage() );
616        }
617    }
618
619
620    /**
621     * {@inheritDoc}
622     */
623    public ArrayTree<V> getDupsContainer( byte[] serialized ) throws IOException
624    {
625        if ( serialized == null )
626        {
627            return new ArrayTree<>( valueComparator );
628        }
629
630        return arrayMarshaller.deserialize( serialized );
631    }
632
633
634    /**
635     * @return the underlying B-tree
636     */
637    protected BTree<K, V> getBTree()
638    {
639        return bt;
640    }
641
642
643    /**
644     * Synchronizes the buffers with disk.
645     *
646     * @throws IOException if errors are encountered on the flush
647     */
648    public synchronized void sync() throws IOException
649    {
650    }
651
652
653    /**
654     * @see Object#toString()
655     */
656    @Override
657    public String toString()
658    {
659        StringBuilder sb = new StringBuilder();
660
661        sb.append( "Mavibot table :\n" ).append( super.toString() );
662
663        return sb.toString();
664    }
665}