001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.core.partition.impl.btree.mavibot;
021
022
023import java.io.File;
024import java.io.IOException;
025import java.net.URI;
026import java.util.Comparator;
027
028import org.apache.directory.api.ldap.model.constants.SchemaConstants;
029import org.apache.directory.api.ldap.model.cursor.Cursor;
030import org.apache.directory.api.ldap.model.cursor.CursorException;
031import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
032import org.apache.directory.api.ldap.model.cursor.Tuple;
033import org.apache.directory.api.ldap.model.exception.LdapException;
034import org.apache.directory.api.ldap.model.exception.LdapOtherException;
035import org.apache.directory.api.ldap.model.schema.AttributeType;
036import org.apache.directory.api.ldap.model.schema.MatchingRule;
037import org.apache.directory.api.ldap.model.schema.SchemaManager;
038import org.apache.directory.api.ldap.model.schema.comparators.SerializableComparator;
039import org.apache.directory.mavibot.btree.RecordManager;
040import org.apache.directory.mavibot.btree.serializer.ByteArraySerializer;
041import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
042import org.apache.directory.mavibot.btree.serializer.StringSerializer;
043import org.apache.directory.server.core.api.partition.PartitionTxn;
044import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
045import org.apache.directory.server.core.partition.impl.btree.IndexCursorAdaptor;
046import org.apache.directory.server.i18n.I18n;
047import org.apache.directory.server.xdbm.AbstractIndex;
048import org.apache.directory.server.xdbm.IndexEntry;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052
053/**
054 * A Mavibot based index implementation. It creates an Index for a give AttributeType.
055 *
056 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
057 */
058public class MavibotIndex<K> extends AbstractIndex<K, String>
059{
060    /** A logger for this class */
061    private static final Logger LOG = LoggerFactory.getLogger( MavibotIndex.class.getSimpleName() );
062
063    /**  the key used for the forward btree name */
064    public static final String FORWARD_BTREE = "_forward";
065
066    /**  the key used for the reverse btree name */
067    public static final String REVERSE_BTREE = "_reverse";
068
069    /**
070     * the forward btree where the btree key is the value of the indexed attribute and
071     * the value of the btree is the entry id of the entry containing an attribute with
072     * that value
073     */
074    protected MavibotTable<K, String> forward;
075
076    /**
077     * the reverse btree where the btree key is the entry id of the entry containing a
078     * value for the indexed attribute, and the btree value is the value of the indexed
079     * attribute
080     */
081    protected MavibotTable<String, K> reverse;
082
083    /** a custom working directory path when specified in configuration */
084    protected File wkDirPath;
085
086    protected RecordManager recordMan;
087
088
089    // ------------------------------------------------------------------------
090    // C O N S T R U C T O R S
091    // ----------------------------------------------------------------------
092    /**
093     * Creates a JdbmIndex instance for a give AttributeId
094     * 
095     * @param attributeId The Attribute ID
096     * @param withReverse If we want a reverse index to be created
097     */
098    public MavibotIndex( String attributeId, boolean withReverse )
099    {
100        super( attributeId, withReverse );
101
102        initialized = false;
103    }
104
105
106    /**
107     * Initialize the index for an Attribute, with a specific working directory (may be null).
108     *
109     * @param schemaManager The schemaManager to use to get back the Attribute
110     * @param attributeType The attributeType this index is created for
111     * @throws IOException If the initialization failed
112     * @throws LdapException If the initialization failed
113     */
114    public void init( SchemaManager schemaManager, AttributeType attributeType ) throws LdapException, IOException
115    {
116        LOG.debug( "Initializing an Index for attribute '{}'", attributeType.getName() );
117
118        // check if the RecordManager reference is null, if yes, then throw an IllegalStateException
119        if ( recordMan == null )
120        {
121            throw new IllegalStateException( "No RecordManager reference was set in the index " + getAttributeId() );
122        }
123
124        this.attributeType = attributeType;
125
126        if ( attributeId == null )
127        {
128            setAttributeId( attributeType.getName() );
129        }
130
131        if ( this.wkDirPath == null )
132        {
133            throw new NullPointerException( "The index working directory has not be set" );
134        }
135
136        try
137        {
138            initTables( schemaManager );
139        }
140        catch ( IOException e )
141        {
142            // clean up
143            close( null );
144            throw e;
145        }
146
147        initialized = true;
148    }
149
150
151    /**
152     * Initializes the forward and reverse tables used by this Index.
153     *
154     * @param schemaManager The server schemaManager
155     * @throws IOException if we cannot initialize the forward and reverse
156     * tables
157     */
158    private void initTables( SchemaManager schemaManager ) throws IOException
159    {
160        MatchingRule mr = attributeType.getEquality();
161
162        if ( mr == null )
163        {
164            throw new IOException( I18n.err( I18n.ERR_574, attributeType.getName() ) );
165        }
166
167        SerializableComparator<K> comp = new SerializableComparator<>( mr.getOid() );
168        comp.setSchemaManager( schemaManager );
169
170        /*
171         * The forward key/value map stores attribute values to master table
172         * primary keys.  A value for an attribute can occur several times in
173         * different entries so the forward map can have more than one value.
174         */
175
176        ElementSerializer<K> forwardKeySerializer = null;
177
178        if ( !attributeType.getSyntax().isHumanReadable() )
179        {
180            forwardKeySerializer = ( ElementSerializer<K> ) new ByteArraySerializer( ( Comparator<byte[]> ) comp );
181        }
182        else
183        {
184            forwardKeySerializer = ( ElementSerializer<K> ) new StringSerializer( ( Comparator<String> ) comp );
185        }
186
187        boolean forwardDups = true;
188
189        String oid = attributeType.getOid();
190        // disable duplicates for entryCSN and entryUUID attribute indices
191        if ( oid.equals( SchemaConstants.ENTRY_CSN_AT_OID ) || oid.equals( SchemaConstants.ENTRY_UUID_AT_OID ) )
192        {
193            forwardDups = false;
194        }
195
196        String forwardTableName = attributeType.getOid() + FORWARD_BTREE;
197        forward = new MavibotTable<>( recordMan, schemaManager, forwardTableName, forwardKeySerializer,
198            StringSerializer.INSTANCE, forwardDups, AbstractBTreePartition.DEFAULT_CACHE_SIZE );
199
200        /*
201         * Now the reverse map stores the primary key into the master table as
202         * the key and the values of attributes as the value.  If an attribute
203         * is single valued according to its specification based on a schema
204         * then duplicate keys should not be allowed within the reverse table.
205         */
206        if ( withReverse )
207        {
208            String reverseTableName = attributeType.getOid() + REVERSE_BTREE;
209            reverse = new MavibotTable<>( recordMan, schemaManager, reverseTableName, StringSerializer.INSTANCE,
210                forwardKeySerializer, !attributeType.isSingleValued() );
211        }
212    }
213
214
215    /**
216     * Sets the RecordManager
217     *
218     * @param rm the RecordManager instance
219     */
220    public void setRecordManager( RecordManager rm )
221    {
222        this.recordMan = rm;
223    }
224
225
226    // ------------------------------------------------------------------------
227    // C O N F I G U R A T I O N   M E T H O D S
228    // ------------------------------------------------------------------------
229
230    /**
231     * Sets the working directory path to something other than the default. Sometimes more
232     * performance is gained by locating indices on separate disk spindles.
233     *
234     * @param wkDirPath optional working directory path
235     */
236    public void setWkDirPath( URI wkDirPath )
237    {
238        //.out.println( "IDX Defining a WorkingDir : " + wkDirPath );
239        protect( "wkDirPath" );
240        this.wkDirPath = new File( wkDirPath );
241    }
242
243
244    /**
245     * Gets the working directory path to something other than the default. Sometimes more
246     * performance is gained by locating indices on separate disk spindles.
247     *
248     * @return optional working directory path
249     */
250    public URI getWkDirPath()
251    {
252        return wkDirPath != null ? wkDirPath.toURI() : null;
253    }
254
255
256    // ------------------------------------------------------------------------
257    // Scan Count Methods
258    // ------------------------------------------------------------------------
259    /**
260     * {@inheritDoc}
261     */
262    public long count( PartitionTxn partitionTxn ) throws LdapException
263    {
264        return forward.count( partitionTxn );
265    }
266
267
268    /**
269     * {@inheritDoc}
270     */
271    public long count( PartitionTxn partitionTxn, K attrVal ) throws LdapException
272    {
273        return forward.count( partitionTxn, attrVal );
274    }
275
276
277    /**
278     * {@inheritDoc}
279     */
280    @Override
281    public long greaterThanCount( PartitionTxn partitionTxn, K attrVal ) throws LdapException
282    {
283        return forward.greaterThanCount( partitionTxn, attrVal );
284    }
285
286
287    /**
288     * {@inheritDoc}
289     */
290    @Override
291    public long lessThanCount( PartitionTxn partitionTxn, K attrVal ) throws LdapException
292    {
293        return forward.lessThanCount( partitionTxn, attrVal );
294    }
295
296
297    // ------------------------------------------------------------------------
298    // Forward and Reverse Lookups
299    // ------------------------------------------------------------------------
300
301    /**
302     * Do a lookup using the forward table
303     * 
304     * @param partitionTxn The Transaction to use
305     * @param attrVal The Key we are looking for
306     * @return The found value
307     * @throws LdapException If the lookup failed
308     */
309    public String forwardLookup( PartitionTxn partitionTxn, K attrVal ) throws LdapException
310    {
311        return forward.get( partitionTxn, attrVal );
312    }
313
314
315    /**
316     * {@inheritDoc}
317     */
318    public K reverseLookup( PartitionTxn partitionTxn, String id ) throws LdapException
319    {
320        if ( withReverse )
321        {
322            return reverse.get( partitionTxn, id );
323        }
324        else
325        {
326            return null;
327        }
328    }
329
330
331    // ------------------------------------------------------------------------
332    // Add/Drop Methods
333    // ------------------------------------------------------------------------
334
335    /**
336     * {@inheritDoc}
337     */
338    public synchronized void add( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException
339    {
340        // The pair to be removed must exists
341        forward.put( partitionTxn, attrVal, id );
342
343        if ( withReverse )
344        {
345            reverse.put( partitionTxn, id, attrVal );
346        }
347    }
348
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    public synchronized void drop( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException
355    {
356        // The pair to be removed must exists
357        if ( forward.has( partitionTxn, attrVal, id ) )
358        {
359            forward.remove( partitionTxn, attrVal, id );
360
361            if ( withReverse )
362            {
363                reverse.remove( partitionTxn, id, attrVal );
364            }
365        }
366    }
367
368
369    /**
370     * {@inheritDoc}
371     */
372    public void drop( PartitionTxn partitionTxn, String entryId ) throws LdapException
373    {
374        if ( withReverse )
375        {
376            if ( isDupsEnabled() )
377            {
378                // Build a cursor to iterate on all the keys referencing
379                // this entryId
380                Cursor<Tuple<String, K>> values = reverse.cursor( partitionTxn, entryId );
381
382                try
383                {
384                    while ( values.next() )
385                    {
386                        // Remove the Key -> entryId from the index
387                        forward.remove( partitionTxn, values.get().getValue(), entryId );
388                    }
389    
390                    values.close();
391                }
392                catch ( CursorException | IOException e )
393                {
394                    throw new LdapOtherException( e.getMessage(), e );
395                }
396            }
397            else
398            {
399                K key = reverse.get( partitionTxn, entryId );
400
401                forward.remove( partitionTxn, key );
402            }
403
404            // Remove the id -> key from the reverse index
405            reverse.remove( partitionTxn, entryId );
406        }
407    }
408
409
410    // ------------------------------------------------------------------------
411    // Index Cursor Operations
412    // ------------------------------------------------------------------------
413    @SuppressWarnings("unchecked")
414    public Cursor<IndexEntry<K, String>> forwardCursor( PartitionTxn partitionTxn ) throws LdapException
415    {
416        return new IndexCursorAdaptor<>( partitionTxn, ( Cursor ) forward.cursor(), true );
417    }
418
419
420    public Cursor<IndexEntry<K, String>> forwardCursor( PartitionTxn partitionTxn, K key ) throws LdapException
421    {
422        return new IndexCursorAdaptor<>( partitionTxn, ( Cursor ) forward.cursor( partitionTxn, key ), true );
423    }
424
425
426    /**
427     * {@inheritDoc}
428     */
429    @Override
430    public Cursor<K> reverseValueCursor( PartitionTxn partitionTxn, String id ) throws LdapException
431    {
432        if ( withReverse )
433        {
434            return reverse.valueCursor( partitionTxn, id );
435        }
436        else
437        {
438            return new EmptyCursor<>();
439        }
440    }
441
442
443    public Cursor<String> forwardValueCursor( PartitionTxn partitionTxn, K key ) throws LdapException
444    {
445        return forward.valueCursor( partitionTxn, key );
446    }
447
448
449    // ------------------------------------------------------------------------
450    // Value Assertion (a.k.a Index Lookup) Methods //
451    // ------------------------------------------------------------------------
452    /**
453     * {@inheritDoc}
454     */
455    public boolean forward( PartitionTxn partitionTxn, K attrVal ) throws LdapException
456    {
457        return forward.has( partitionTxn, attrVal );
458    }
459
460
461    /**
462     * {@inheritDoc}
463     */
464    public boolean forward( PartitionTxn partitionTxn, K attrVal, String id ) throws LdapException
465    {
466        return forward.has( partitionTxn, attrVal, id );
467    }
468
469
470    /**
471     * {@inheritDoc}
472     */
473    @Override
474    public boolean reverse( PartitionTxn partitionTxn, String id ) throws LdapException
475    {
476        if ( withReverse )
477        {
478            return reverse.has( partitionTxn, id );
479        }
480        else
481        {
482            return false;
483        }
484    }
485
486
487    /**
488     * {@inheritDoc}
489     */
490    @Override
491    public boolean reverse( PartitionTxn partitionTxn, String id, K attrVal ) throws LdapException
492    {
493        return forward.has( partitionTxn, attrVal, id );
494    }
495
496
497    // ------------------------------------------------------------------------
498    // Maintenance Methods
499    // ------------------------------------------------------------------------
500    /**
501     * {@inheritDoc}
502     */
503    @Override
504    public synchronized void close( PartitionTxn partitionTxn ) throws IOException
505    {
506        try
507        {
508            if ( forward != null )
509            {
510                forward.close( partitionTxn );
511            }
512
513            if ( reverse != null )
514            {
515                reverse.close( partitionTxn );
516            }
517        }
518        catch ( Exception e )
519        {
520            throw new IOException( e );
521        }
522    }
523
524
525    /**
526     * Force the flush of this index
527     * 
528     * @throws IOException If the flush failed
529     */
530    public synchronized void sync() throws IOException
531    {
532        forward.getBTree().flush();
533        
534        if ( reverse != null )
535        {
536            reverse.getBTree().flush();
537        }
538    }
539
540
541    /**
542     * {@inheritDoc}
543     */
544    @Override
545    public boolean isDupsEnabled()
546    {
547        if ( withReverse )
548        {
549            return reverse.isDupsEnabled();
550        }
551        else
552        {
553            return false;
554        }
555    }
556
557
558    /**
559     * @see Object#toString()
560     */
561    public String toString()
562    {
563        return "Index<" + attributeId + ">";
564    }
565}