View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.partition.impl.btree.mavibot;
21  
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.net.URI;
26  import java.util.Comparator;
27  
28  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
29  import org.apache.directory.api.ldap.model.cursor.Cursor;
30  import org.apache.directory.api.ldap.model.cursor.CursorException;
31  import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
32  import org.apache.directory.api.ldap.model.cursor.Tuple;
33  import org.apache.directory.api.ldap.model.exception.LdapException;
34  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
35  import org.apache.directory.api.ldap.model.schema.AttributeType;
36  import org.apache.directory.api.ldap.model.schema.MatchingRule;
37  import org.apache.directory.api.ldap.model.schema.SchemaManager;
38  import org.apache.directory.api.ldap.model.schema.comparators.SerializableComparator;
39  import org.apache.directory.mavibot.btree.RecordManager;
40  import org.apache.directory.mavibot.btree.serializer.ByteArraySerializer;
41  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
42  import org.apache.directory.mavibot.btree.serializer.StringSerializer;
43  import org.apache.directory.server.core.api.partition.PartitionTxn;
44  import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
45  import org.apache.directory.server.core.partition.impl.btree.IndexCursorAdaptor;
46  import org.apache.directory.server.i18n.I18n;
47  import org.apache.directory.server.xdbm.AbstractIndex;
48  import org.apache.directory.server.xdbm.IndexEntry;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  
53  /**
54   * A Mavibot based index implementation. It creates an Index for a give AttributeType.
55   *
56   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
57   */
58  public class MavibotIndex<K> extends AbstractIndex<K, String>
59  {
60      /** A logger for this class */
61      private static final Logger LOG = LoggerFactory.getLogger( MavibotIndex.class.getSimpleName() );
62  
63      /**  the key used for the forward btree name */
64      public static final String FORWARD_BTREE = "_forward";
65  
66      /**  the key used for the reverse btree name */
67      public static final String REVERSE_BTREE = "_reverse";
68  
69      /**
70       * the forward btree where the btree key is the value of the indexed attribute and
71       * the value of the btree is the entry id of the entry containing an attribute with
72       * that value
73       */
74      protected MavibotTable<K, String> forward;
75  
76      /**
77       * the reverse btree where the btree key is the entry id of the entry containing a
78       * value for the indexed attribute, and the btree value is the value of the indexed
79       * attribute
80       */
81      protected MavibotTable<String, K> reverse;
82  
83      /** a custom working directory path when specified in configuration */
84      protected File wkDirPath;
85  
86      protected RecordManager recordMan;
87  
88  
89      // ------------------------------------------------------------------------
90      // C O N S T R U C T O R S
91      // ----------------------------------------------------------------------
92      /**
93       * Creates a JdbmIndex instance for a give AttributeId
94       * 
95       * @param attributeId The Attribute ID
96       * @param withReverse If we want a reverse index to be created
97       */
98      public MavibotIndex( String attributeId, boolean withReverse )
99      {
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 }