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  
21  package org.apache.directory.server.core.partition.ldif;
22  
23  
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.RandomAccessFile;
28  import java.util.Iterator;
29  import java.util.UUID;
30  
31  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
32  import org.apache.directory.api.ldap.model.cursor.Cursor;
33  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
34  import org.apache.directory.api.ldap.model.entry.Entry;
35  import org.apache.directory.api.ldap.model.entry.Modification;
36  import org.apache.directory.api.ldap.model.exception.LdapException;
37  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
38  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
39  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
40  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
41  import org.apache.directory.api.ldap.model.ldif.LdifReader;
42  import org.apache.directory.api.ldap.model.ldif.LdifUtils;
43  import org.apache.directory.api.ldap.model.name.Dn;
44  import org.apache.directory.api.ldap.model.name.Rdn;
45  import org.apache.directory.api.ldap.model.schema.SchemaManager;
46  import org.apache.directory.api.util.Strings;
47  import org.apache.directory.server.core.api.DnFactory;
48  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
51  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
52  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
53  import org.apache.directory.server.core.api.partition.PartitionTxn;
54  import org.apache.directory.server.i18n.I18n;
55  import org.apache.directory.server.xdbm.IndexEntry;
56  import org.apache.directory.server.xdbm.ParentIdAndRdn;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  
61  /**
62   * A Partition implementation backed by a single LDIF file.
63   *
64   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
65   */
66  public class SingleFileLdifPartition extends AbstractLdifPartition
67  {
68      /** the LDIF file holding the partition's data */
69      private RandomAccessFile ldifFile;
70  
71      /** flag to enable/disable re-writing in-memory partition data back to file, default is set to true */
72      private volatile boolean enableRewriting = true;
73  
74      /** flag used internally to detect if partition data was updated in memory but not on disk */
75      private boolean dirty = false;
76  
77      /** lock for serializing the operations on the backing LDIF file */
78      private Object lock = new Object();
79  
80      private static final Logger LOG = LoggerFactory.getLogger( SingleFileLdifPartition.class );
81  
82  
83      /**
84       * Creates a new instance of SingleFileLdifPartition.
85       * 
86       * @param schemaManager The SchemaManager instance
87       * @param dnFactory The DN factory
88       */
89      public SingleFileLdifPartition( SchemaManager schemaManager, DnFactory dnFactory )
90      {
91          super( schemaManager, dnFactory );
92      }
93  
94  
95      @Override
96      protected void doInit() throws LdapException
97      {
98          if ( !initialized )
99          {
100             if ( getPartitionPath() == null )
101             {
102                 throw new IllegalArgumentException( "Partition path cannot be null" );
103             }
104 
105             File partitionFile = new File( getPartitionPath() );
106 
107             if ( partitionFile.exists() && !partitionFile.isFile() )
108             {
109                 throw new IllegalArgumentException( "Partition path must be a LDIF file" );
110             }
111 
112             try
113             {
114                 ldifFile = new RandomAccessFile( partitionFile, "rws" );
115             }
116             catch ( FileNotFoundException fnfe )
117             {
118                 throw new LdapOtherException( fnfe.getMessage(), fnfe );
119             }
120             
121             LOG.debug( "id is : {}", getId() );
122 
123             // Initialize the suffixDirectory : it's a composition
124             // of the workingDirectory followed by the suffix
125             if ( ( suffixDn == null ) || ( suffixDn.isEmpty() ) )
126             {
127                 String msg = I18n.err( I18n.ERR_150 );
128                 LOG.error( msg );
129                 throw new LdapInvalidDnException( msg );
130             }
131 
132             if ( !suffixDn.isSchemaAware() )
133             {
134                 suffixDn = new Dn( schemaManager, suffixDn );
135             }
136 
137             super.doInit();
138 
139             loadEntries();
140         }
141     }
142 
143 
144     /**
145      * load the entries from the LDIF file if present
146      * @throws Exception
147      */
148     private void loadEntries() throws LdapException
149     {
150         try ( RandomAccessLdifReader parser = new RandomAccessLdifReader( schemaManager ) )
151         {
152             Iterator<LdifEntry> itr = parser.iterator();
153     
154             if ( !itr.hasNext() )
155             {
156                 return;
157             }
158     
159             LdifEntry ldifEntry = itr.next();
160     
161             contextEntry = new DefaultEntry( schemaManager, ldifEntry.getEntry() );
162     
163             if ( suffixDn.equals( contextEntry.getDn() ) )
164             {
165                 addMandatoryOpAt( contextEntry );
166     
167                 AddOperationContext/interceptor/context/AddOperationContext.html#AddOperationContext">AddOperationContext addContext = new AddOperationContext( null, contextEntry );
168                 addContext.setPartition( this );
169                 addContext.setTransaction( this.beginWriteTransaction() );
170 
171                 super.add( addContext );
172             }
173             else
174             {
175                 throw new LdapException( "The given LDIF file doesn't contain the context entry" );
176             }
177     
178             while ( itr.hasNext() )
179             {
180                 ldifEntry = itr.next();
181     
182                 Entry entry = new DefaultEntry( schemaManager, ldifEntry.getEntry() );
183     
184                 addMandatoryOpAt( entry );
185     
186                 AddOperationContext/interceptor/context/AddOperationContext.html#AddOperationContext">AddOperationContext addContext = new AddOperationContext( null, entry );
187                 addContext.setPartition( this );
188                 addContext.setTransaction( this.beginWriteTransaction() );
189 
190                 super.add( addContext );
191             }
192         }
193         catch ( IOException ioe )
194         {
195             throw new LdapOtherException( ioe.getMessage(), ioe );
196         }
197     }
198 
199 
200     //---------------------------------------------------------------------------------------------
201     // Operations
202     //---------------------------------------------------------------------------------------------
203     /**
204      * {@inheritDoc}
205      */
206     @Override
207     public void add( AddOperationContext addContext ) throws LdapException
208     {
209         synchronized ( lock )
210         {
211             super.add( addContext );
212 
213             if ( contextEntry == null )
214             {
215                 Entry entry = addContext.getEntry();
216 
217                 if ( entry.getDn().equals( suffixDn ) )
218                 {
219                     contextEntry = entry;
220                 }
221             }
222 
223             dirty = true;
224             rewritePartitionData( addContext.getTransaction() );
225         }
226     }
227 
228 
229     /**
230      * {@inheritDoc}
231      */
232     @Override
233     public void modify( ModifyOperationContext modifyContext ) throws LdapException
234     {
235         PartitionTxn partitionTxn = modifyContext.getTransaction();
236         
237         synchronized ( lock )
238         {
239             try
240             {
241                 Entry modifiedEntry = super.modify( partitionTxn, modifyContext.getDn(),
242                     modifyContext.getModItems().toArray( new Modification[]
243                         {} ) );
244 
245                 // Remove the EntryDN
246                 modifiedEntry.removeAttributes( entryDnAT );
247 
248                 modifyContext.setAlteredEntry( modifiedEntry );
249             }
250             catch ( Exception e )
251             {
252                 throw new LdapOperationException( e.getMessage(), e );
253             }
254 
255             dirty = true;
256             rewritePartitionData( partitionTxn );
257         }
258     }
259 
260 
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     public void rename( RenameOperationContext renameContext ) throws LdapException
266     {
267         synchronized ( lock )
268         {
269             super.rename( renameContext );
270             dirty = true;
271             rewritePartitionData( renameContext.getTransaction() );
272         }
273     }
274 
275 
276     /**
277      * {@inheritDoc}
278      */
279     @Override
280     public void move( MoveOperationContext moveContext ) throws LdapException
281     {
282         synchronized ( lock )
283         {
284             super.move( moveContext );
285             dirty = true;
286             rewritePartitionData( moveContext.getTransaction() );
287         }
288     }
289 
290 
291     /**
292      * {@inheritDoc}
293      */
294     @Override
295     public void moveAndRename( MoveAndRenameOperationContext opContext ) throws LdapException
296     {
297         synchronized ( lock )
298         {
299             super.moveAndRename( opContext );
300             dirty = true;
301             rewritePartitionData( opContext.getTransaction() );
302         }
303     }
304 
305 
306     @Override
307     public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException
308     {
309         synchronized ( lock )
310         {
311             Entry deletedEntry = super.delete( partitionTxn, id );
312             dirty = true;
313             rewritePartitionData( partitionTxn );
314 
315             return deletedEntry;
316         }
317     }
318 
319 
320     /**
321      * writes the partition's data to the file if {@link #enableRewriting} is set to true
322      * and partition was modified since the last write or {@link #dirty} data. 
323      * 
324      * @throws LdapException
325      */
326     private void rewritePartitionData( PartitionTxn partitionTxn ) throws LdapException
327     {
328         synchronized ( lock )
329         {
330             if ( !enableRewriting || !dirty )
331             {
332                 return;
333             }
334 
335             try
336             {
337                 ldifFile.setLength( 0 ); // wipe the file clean
338 
339                 String suffixId = getEntryId( partitionTxn, suffixDn );
340 
341                 if ( suffixId == null )
342                 {
343                     contextEntry = null;
344                     return;
345                 }
346 
347                 ParentIdAndRdn suffixEntry = rdnIdx.reverseLookup( partitionTxn, suffixId );
348 
349                 if ( suffixEntry != null )
350                 {
351                     Entry entry = master.get( partitionTxn, suffixId );
352 
353                     // Don't write the EntryDN attribute
354                     entry.removeAttributes( entryDnAT );
355 
356                     entry.setDn( suffixDn );
357 
358                     appendLdif( entry );
359 
360                     appendRecursive( partitionTxn, suffixId, suffixEntry.getNbChildren() );
361                 }
362 
363                 dirty = false;
364             }
365             catch ( LdapException e )
366             {
367                 throw e;
368             }
369             catch ( Exception e )
370             {
371                 throw new LdapException( e );
372             }
373         }
374     }
375 
376 
377     private void appendRecursive( PartitionTxn partitionTxn, String id, int nbSibbling ) throws Exception
378     {
379         // Start with the root
380         Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn );
381 
382         IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
383         startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) );
384         cursor.before( startingPos );
385         int countChildren = 0;
386 
387         while ( cursor.next() && ( countChildren < nbSibbling ) )
388         {
389             IndexEntry<ParentIdAndRdn, String> element = cursor.get();
390             String childId = element.getId();
391             Entry entry = fetch( partitionTxn, childId );
392 
393             // Remove the EntryDn
394             entry.removeAttributes( SchemaConstants.ENTRY_DN_AT );
395 
396             appendLdif( entry );
397 
398             countChildren++;
399 
400             // And now, the children
401             int nbChildren = element.getKey().getNbChildren();
402 
403             if ( nbChildren > 0 )
404             {
405                 appendRecursive( partitionTxn, childId, nbChildren );
406             }
407         }
408 
409         cursor.close();
410     }
411 
412 
413     /**
414      * append data to the LDIF file
415      *
416      * @param entry the entry to be written
417      * @throws LdapException
418      */
419     private void appendLdif( Entry entry ) throws IOException
420     {
421         synchronized ( lock )
422         {
423             String ldif = LdifUtils.convertToLdif( entry );
424             ldifFile.write( Strings.getBytesUtf8( ldif + "\n" ) );
425         }
426     }
427 
428     /**
429      * an LdifReader backed by a RandomAccessFile
430      */
431     private class RandomAccessLdifReader extends LdifReader
432     {
433         private long len;
434 
435 
436         RandomAccessLdifReader() throws LdapException
437         {
438             try
439             {
440                 len = ldifFile.length();
441                 super.init();
442             }
443             catch ( IOException e )
444             {
445                 LdapException le = new LdapException( e.getMessage(), e );
446                 le.initCause( e );
447 
448                 throw le;
449             }
450         }
451 
452 
453         RandomAccessLdifReader( SchemaManager schemaManager ) throws LdapException
454         {
455             try
456             {
457                 this.schemaManager = schemaManager;
458                 len = ldifFile.length();
459                 super.init();
460             }
461             catch ( IOException e )
462             {
463                 LdapException le = new LdapException( e.getMessage(), e );
464                 le.initCause( e );
465 
466                 throw le;
467             }
468         }
469 
470 
471         @Override
472         protected String getLine() throws IOException
473         {
474             if ( len == 0 )
475             {
476                 return null;
477             }
478 
479             return ldifFile.readLine();
480         }
481     }
482 
483 
484     /**
485      * add the CSN and UUID attributes to the entry if they are not present
486      */
487     private void addMandatoryOpAt( Entry entry ) throws LdapException
488     {
489         if ( entry.get( SchemaConstants.ENTRY_CSN_AT ) == null )
490         {
491             entry.add( SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString() );
492         }
493 
494         if ( entry.get( SchemaConstants.ENTRY_UUID_AT ) == null )
495         {
496             String uuid = UUID.randomUUID().toString();
497             entry.add( SchemaConstants.ENTRY_UUID_AT, uuid );
498         }
499     }
500 
501 
502     /**
503      * {@inheritDoc}
504      */
505     @Override
506     protected void doDestroy( PartitionTxn partitionTxn ) throws LdapException
507     {
508         super.doDestroy( partitionTxn );
509         
510         try
511         {
512             ldifFile.close();
513         }
514         catch ( IOException ioe )
515         {
516             throw new LdapOtherException( ioe.getMessage(), ioe );
517         }
518     }
519 
520 
521     /**
522      * enable/disable the re-writing of partition data.
523      * This method internally calls the rewritePartitionData() method to save any dirty data if present
524      * 
525      * @param partitionTxn The transaction to use
526      * @param enableRewriting flag to enable/disable re-writing
527      * @throws LdapException If we weren't able to save the dirty data
528      */
529     public void setEnableRewriting( PartitionTxn partitionTxn, boolean enableRewriting ) throws LdapException
530     {
531         this.enableRewriting = enableRewriting;
532 
533         // save data if found dirty 
534         rewritePartitionData( partitionTxn );
535     }
536 }