1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.server.core.partition.ldif;
21
22
23 import java.io.File;
24 import java.io.FileFilter;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.UUID;
32
33 import org.apache.directory.api.ldap.model.constants.SchemaConstants;
34 import org.apache.directory.api.ldap.model.csn.CsnFactory;
35 import org.apache.directory.api.ldap.model.cursor.Cursor;
36 import org.apache.directory.api.ldap.model.entry.DefaultEntry;
37 import org.apache.directory.api.ldap.model.entry.Entry;
38 import org.apache.directory.api.ldap.model.entry.Modification;
39 import org.apache.directory.api.ldap.model.exception.LdapException;
40 import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
41 import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
42 import org.apache.directory.api.ldap.model.exception.LdapOperationException;
43 import org.apache.directory.api.ldap.model.exception.LdapOtherException;
44 import org.apache.directory.api.ldap.model.ldif.LdifEntry;
45 import org.apache.directory.api.ldap.model.ldif.LdifReader;
46 import org.apache.directory.api.ldap.model.ldif.LdifUtils;
47 import org.apache.directory.api.ldap.model.name.Ava;
48 import org.apache.directory.api.ldap.model.name.Dn;
49 import org.apache.directory.api.ldap.model.name.Rdn;
50 import org.apache.directory.api.ldap.model.schema.AttributeType;
51 import org.apache.directory.api.ldap.model.schema.SchemaManager;
52 import org.apache.directory.api.util.Strings;
53 import org.apache.directory.server.core.api.DnFactory;
54 import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
55 import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
56 import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
57 import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
58 import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
59 import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
60 import org.apache.directory.server.core.api.partition.PartitionTxn;
61 import org.apache.directory.server.i18n.I18n;
62 import org.apache.directory.server.xdbm.IndexEntry;
63 import org.apache.directory.server.xdbm.ParentIdAndRdn;
64 import org.apache.directory.server.xdbm.SingletonIndexCursor;
65 import org.apache.directory.server.xdbm.search.cursor.DescendantCursor;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public class LdifPartition extends AbstractLdifPartition
96 {
97
98 private static final Logger LOG = LoggerFactory.getLogger( LdifPartition.class );
99
100
101 private File suffixDirectory;
102
103
104 private static final boolean CREATE = Boolean.TRUE;
105 private static final boolean DELETE = Boolean.FALSE;
106
107
108 private FileFilter dirFilter = new FileFilter()
109 {
110 public boolean accept( File dir )
111 {
112 return dir.isDirectory();
113 }
114 };
115
116
117 private FileFilter entryFilter = new FileFilter()
118 {
119 public boolean accept( File dir )
120 {
121 if ( dir.getName().endsWith( CONF_FILE_EXTN ) )
122 {
123 return dir.isFile();
124 }
125 else
126 {
127 return false;
128 }
129 }
130 };
131
132
133
134
135
136
137
138
139 public LdifPartition( SchemaManager schemaManager, DnFactory dnFactory )
140 {
141 super( schemaManager, dnFactory );
142 }
143
144
145
146
147
148 @Override
149 protected void doInit() throws LdapException
150 {
151 if ( !initialized )
152 {
153 File partitionDir = new File( getPartitionPath() );
154
155
156
157 if ( ( suffixDn == null ) || ( suffixDn.isEmpty() ) )
158 {
159 String msg = I18n.err( I18n.ERR_150 );
160 LOG.error( msg );
161 throw new LdapInvalidDnException( msg );
162 }
163
164 if ( !suffixDn.isSchemaAware() )
165 {
166 suffixDn = new Dn( schemaManager, suffixDn );
167 }
168
169 String suffixDirName = getFileName( suffixDn );
170 suffixDirectory = new File( partitionDir, suffixDirName );
171
172 super.doInit();
173
174
175
176 if ( suffixDirectory.exists() )
177 {
178 loadEntries( partitionDir );
179 }
180 else
181 {
182
183 try
184 {
185 suffixDirectory.mkdirs();
186 }
187 catch ( SecurityException se )
188 {
189 String msg = I18n.err( I18n.ERR_151, suffixDirectory.getAbsolutePath(), se.getLocalizedMessage() );
190 LOG.error( msg );
191 throw se;
192 }
193
194
195 File contextEntryFile = new File( suffixDirectory + CONF_FILE_EXTN );
196
197 LOG.info( "ldif file doesn't exist {}, creating it.", contextEntryFile.getAbsolutePath() );
198
199 if ( contextEntry == null )
200 {
201 if ( contextEntryFile.exists() )
202 {
203 try ( LdifReader reader = new LdifReader( contextEntryFile ) )
204 {
205 contextEntry = new DefaultEntry( schemaManager, reader.next().getEntry() );
206 }
207 catch ( IOException ioe )
208 {
209 throw new LdapOtherException( ioe.getMessage(), ioe );
210 }
211 }
212 else
213 {
214
215
216 return;
217 }
218 }
219
220
221 if ( suffixDn != null )
222 {
223 Dn contextEntryDn = contextEntry.getDn();
224
225
226 if ( !contextEntryDn.isSchemaAware() )
227 {
228 contextEntryDn = new Dn( schemaManager, contextEntryDn );
229 }
230
231
232 if ( suffixDn.equals( contextEntryDn ) )
233 {
234
235 Entry suffixEntry;
236
237 LookupOperationContextceptor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( null, suffixDn );
238 lookupContext.setPartition( this );
239
240 try ( PartitionTxn partitionTxn = this.beginReadTransaction() )
241 {
242 lookupContext.setTransaction( partitionTxn );
243 suffixEntry = lookup( lookupContext );
244 }
245 catch ( IOException ioe )
246 {
247 throw new LdapOtherException( ioe.getMessage(), ioe );
248 }
249
250
251 if ( suffixEntry == null )
252 {
253
254 if ( !contextEntry.isSchemaAware() )
255 {
256
257 contextEntry = new DefaultEntry( schemaManager, contextEntry );
258 }
259
260
261 if ( contextEntry.get( SchemaConstants.ENTRY_CSN_AT ) == null )
262 {
263 contextEntry.add( SchemaConstants.ENTRY_CSN_AT, new CsnFactory( 0 ).newInstance()
264 .toString() );
265 }
266
267
268 if ( contextEntry.get( SchemaConstants.ENTRY_UUID_AT ) == null )
269 {
270 String uuid = UUID.randomUUID().toString();
271 contextEntry.add( SchemaConstants.ENTRY_UUID_AT, uuid );
272 }
273
274
275 AddOperationContext/interceptor/context/AddOperationContext.html#AddOperationContext">AddOperationContext addContext = new AddOperationContext( null, contextEntry );
276 addContext.setPartition( this );
277 PartitionTxn partitionTxn = null;
278
279 try
280 {
281 partitionTxn = beginWriteTransaction();
282 addContext.setTransaction( partitionTxn );
283
284 add( addContext );
285 partitionTxn.commit();
286 }
287 catch ( Exception e )
288 {
289 try
290 {
291 if ( partitionTxn != null )
292 {
293 partitionTxn.abort();
294 }
295 }
296 catch ( IOException ioe )
297 {
298 throw new LdapOtherException( ioe.getMessage(), ioe );
299 }
300 }
301 }
302 }
303 }
304 }
305 }
306 }
307
308
309
310
311
312
313
314
315 @Override
316 public void add( AddOperationContext addContext ) throws LdapException
317 {
318 super.add( addContext );
319
320 addEntry( addContext.getEntry() );
321 }
322
323
324
325
326
327 @Override
328 public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException
329 {
330 Entry deletedEntry = super.delete( partitionTxn, id );
331
332 if ( deletedEntry != null )
333 {
334 File ldifFile = getFile( deletedEntry.getDn(), DELETE );
335
336 boolean deleted = deleteFile( ldifFile );
337
338 LOG.debug( "deleted file {} {}", ldifFile.getAbsoluteFile(), deleted );
339
340
341 File parentFile = ldifFile.getParentFile();
342
343 if ( parentFile.listFiles().length == 0 )
344 {
345 deleteFile( parentFile );
346
347 LOG.debug( "deleted file {} {}", parentFile.getAbsoluteFile(), deleted );
348 }
349 }
350
351 return deletedEntry;
352 }
353
354
355
356
357
358 @Override
359 public void modify( ModifyOperationContext modifyContext ) throws LdapException
360 {
361 PartitionTxn partitionTxn = modifyContext.getTransaction();
362 String id = getEntryId( partitionTxn, modifyContext.getDn() );
363
364 try
365 {
366 super.modify( modifyContext.getTransaction(), modifyContext.getDn(), modifyContext.getModItems().toArray( new Modification[]
367 {} ) );
368 }
369 catch ( Exception e )
370 {
371 throw new LdapOperationException( e.getMessage(), e );
372 }
373
374
375 Entry modifiedEntry = fetch( modifyContext.getTransaction(), id, modifyContext.getDn() );
376 modifyContext.setAlteredEntry( modifiedEntry );
377
378
379 modifiedEntry.removeAttributes( entryDnAT );
380
381
382 Dn dn = modifyContext.getDn();
383
384
385
386 try ( Writer fw = Files.newBufferedWriter( getFile( dn, DELETE ).toPath(), StandardCharsets.UTF_8 ) )
387 {
388 fw.write( LdifUtils.convertToLdif( modifiedEntry, true ) );
389 }
390 catch ( IOException ioe )
391 {
392 throw new LdapOperationException( ioe.getMessage(), ioe );
393 }
394 }
395
396
397
398
399
400 @Override
401 public void move( MoveOperationContext moveContext ) throws LdapException
402 {
403 PartitionTxn partitionTxn = moveContext.getTransaction();
404 Dn oldDn = moveContext.getDn();
405 String id = getEntryId( partitionTxn, oldDn );
406
407 super.move( moveContext );
408
409
410 Entry modifiedEntry = fetch( moveContext.getTransaction(), id, moveContext.getNewDn() );
411
412 try
413 {
414 entryMoved( partitionTxn, oldDn, modifiedEntry, id );
415 }
416 catch ( Exception e )
417 {
418 throw new LdapOperationErrorException( e.getMessage(), e );
419 }
420 }
421
422
423
424
425
426 @Override
427 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
428 {
429 PartitionTxn partitionTxn = moveAndRenameContext.getTransaction();
430 Dn oldDn = moveAndRenameContext.getDn();
431 String id = getEntryId( partitionTxn, oldDn );
432
433 super.moveAndRename( moveAndRenameContext );
434
435
436 Entry modifiedEntry = fetch( moveAndRenameContext.getTransaction(), id, moveAndRenameContext.getNewDn() );
437 moveAndRenameContext.setModifiedEntry( modifiedEntry );
438
439 try
440 {
441 entryMoved( partitionTxn, oldDn, modifiedEntry, id );
442 }
443 catch ( Exception e )
444 {
445 throw new LdapOperationErrorException( e.getMessage(), e );
446 }
447 }
448
449
450
451
452
453 @Override
454 public void rename( RenameOperationContext renameContext ) throws LdapException
455 {
456 PartitionTxn partitionTxn = renameContext.getTransaction();
457 Dn oldDn = renameContext.getDn();
458 String entryId = getEntryId( partitionTxn, oldDn );
459
460
461 super.rename( renameContext );
462
463
464 Dn newDn = oldDn.getParent().add( renameContext.getNewRdn() );
465 Entry modifiedEntry = fetch( renameContext.getTransaction(), entryId, newDn );
466 renameContext.setModifiedEntry( modifiedEntry );
467
468
469
470 try
471 {
472 entryMoved( partitionTxn, oldDn, modifiedEntry, entryId );
473 }
474 catch ( Exception e )
475 {
476 throw new LdapOperationErrorException( e.getMessage(), e );
477 }
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491 private void entryMoved( PartitionTxn partitionTxn, Dn oldEntryDn, Entry modifiedEntry, String entryIdOld ) throws LdapException
492 {
493
494 addEntry( modifiedEntry );
495
496 String baseId = getEntryId( partitionTxn, modifiedEntry.getDn() );
497
498 ParentIdAndRdn parentIdAndRdn = getRdnIndex().reverseLookup( partitionTxn, baseId );
499 IndexEntry/xdbm/IndexEntry.html#IndexEntry">IndexEntry indexEntry = new IndexEntry();
500
501 indexEntry.setId( baseId );
502 indexEntry.setKey( parentIdAndRdn );
503
504 Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = new SingletonIndexCursor<>( partitionTxn, indexEntry );
505 String parentId = parentIdAndRdn.getParentId();
506
507 Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( partitionTxn, this, baseId, parentId, cursor );
508
509
510 try
511 {
512 while ( scopeCursor.next() )
513 {
514 IndexEntry<String, String> entry = scopeCursor.get();
515
516
517 if ( entry.getId() != entryIdOld )
518 {
519 addEntry( fetch( partitionTxn, entry.getId() ) );
520 }
521 }
522
523 scopeCursor.close();
524 }
525 catch ( Exception e )
526 {
527 throw new LdapOperationException( e.getMessage(), e );
528 }
529
530
531 File file = getFile( oldEntryDn, DELETE );
532 boolean deleted = deleteFile( file );
533 LOG.warn( "move operation: deleted file {} {}", file.getAbsoluteFile(), deleted );
534
535
536 String dirName = file.getAbsolutePath();
537 dirName = dirName.substring( 0, dirName.indexOf( CONF_FILE_EXTN ) );
538 deleted = deleteFile( new File( dirName ) );
539 LOG.warn( "move operation: deleted dir {} {}", dirName, deleted );
540 }
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 private void loadEntries( File entryDir ) throws LdapException
560 {
561 LOG.debug( "Processing dir {}", entryDir.getName() );
562
563
564 File[] entries = entryDir.listFiles( entryFilter );
565
566 if ( ( entries != null ) && ( entries.length != 0 ) )
567 {
568 LdifReader ldifReader = new LdifReader( schemaManager );
569
570 for ( File entry : entries )
571 {
572 LOG.debug( "parsing ldif file {}", entry.getName() );
573 List<LdifEntry> ldifEntries = ldifReader.parseLdifFile( entry.getAbsolutePath() );
574
575 try
576 {
577 ldifReader.close();
578 }
579 catch ( IOException ioe )
580 {
581 throw new LdapOtherException( ioe.getMessage(), ioe );
582 }
583
584 if ( ( ldifEntries != null ) && !ldifEntries.isEmpty() )
585 {
586
587 LdifEntry ldifEntry = ldifEntries.get( 0 );
588 LOG.debug( "Adding entry {}", ldifEntry );
589
590 Entry serverEntry = new DefaultEntry( schemaManager, ldifEntry.getEntry() );
591
592 if ( !serverEntry.containsAttribute( SchemaConstants.ENTRY_CSN_AT ) )
593 {
594 serverEntry.put( SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString() );
595 }
596
597 if ( !serverEntry.containsAttribute( SchemaConstants.ENTRY_UUID_AT ) )
598 {
599 serverEntry.put( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
600 }
601
602
603 AddOperationContext/interceptor/context/AddOperationContext.html#AddOperationContext">AddOperationContext addContext = new AddOperationContext( null, serverEntry );
604 PartitionTxn partitionTxn = beginWriteTransaction();
605
606 try
607 {
608 addContext.setTransaction( partitionTxn );
609 addContext.setPartition( this );
610
611 super.add( addContext );
612
613 partitionTxn.commit();
614 }
615 catch ( LdapException le )
616 {
617 try
618 {
619 partitionTxn.abort();
620 }
621 catch ( IOException ioe )
622 {
623 throw new LdapOtherException( ioe.getMessage(), ioe );
624 }
625
626 throw le;
627 }
628 catch ( IOException ioe )
629 {
630 try
631 {
632 partitionTxn.abort();
633 }
634 catch ( IOException ioe2 )
635 {
636 throw new LdapOtherException( ioe2.getMessage(), ioe2 );
637 }
638
639 throw new LdapOtherException( ioe.getMessage(), ioe );
640 }
641 }
642 }
643
644 }
645 else
646 {
647
648 return;
649 }
650
651
652 File[] dirs = entryDir.listFiles( dirFilter );
653
654 if ( ( dirs != null ) && ( dirs.length != 0 ) )
655 {
656 for ( File f : dirs )
657 {
658 loadEntries( f );
659 }
660 }
661 }
662
663
664
665
666
667 private File getFile( Dn entryDn, boolean create ) throws LdapException
668 {
669 String parentDir = null;
670 String rdnFileName = null;
671
672 if ( entryDn.equals( suffixDn ) )
673 {
674 parentDir = suffixDirectory.getParent() + File.separator;
675 rdnFileName = suffixDn.getName() + CONF_FILE_EXTN;
676 }
677 else
678 {
679 StringBuilder filePath = new StringBuilder();
680 filePath.append( suffixDirectory ).append( File.separator );
681
682 Dn baseDn = entryDn.getDescendantOf( suffixDn );
683 int size = baseDn.size();
684
685 for ( int i = 0; i < size - 1; i++ )
686 {
687 rdnFileName = getFileName( baseDn.getRdn( size - 1 - i ) );
688
689 filePath.append( rdnFileName ).append( File.separator );
690 }
691
692 rdnFileName = getFileName( entryDn.getRdn() ) + CONF_FILE_EXTN;
693 parentDir = filePath.toString();
694 }
695
696 File dir = new File( parentDir );
697
698 if ( !dir.exists() && create )
699 {
700
701 if ( !dir.mkdir() )
702 {
703 throw new LdapException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, dir ) );
704 }
705 }
706
707 File ldifFile = new File( parentDir + rdnFileName );
708
709 if ( ldifFile.exists() && create )
710 {
711
712 throw new LdapException( I18n.err( I18n.ERR_633 ) );
713 }
714
715 return ldifFile;
716 }
717
718
719
720
721
722
723
724
725 private String getFileName( Rdn rdn ) throws LdapException
726 {
727 StringBuilder fileName = new StringBuilder( "" );
728
729 Iterator<Ava> iterator = rdn.iterator();
730
731 while ( iterator.hasNext() )
732 {
733 Ava ava = iterator.next();
734
735
736 String normAT = ava.getNormType();
737 AttributeType at = schemaManager.lookupAttributeTypeRegistry( normAT );
738
739 String atName = at.getName();
740
741
742 String normValue = null;
743
744 if ( at.getSyntax().isHumanReadable() )
745 {
746 normValue = ava.getValue().getString();
747 }
748 else
749 {
750 normValue = Strings.utf8ToString( ava.getValue().getBytes() );
751 }
752
753 fileName.append( atName ).append( "=" ).append( normValue );
754
755 if ( iterator.hasNext() )
756 {
757 fileName.append( "+" );
758 }
759 }
760
761 return getOSFileName( fileName.toString() );
762 }
763
764
765
766
767
768
769
770
771 private String getFileName( Dn dn ) throws LdapException
772 {
773 StringBuilder sb = new StringBuilder();
774 boolean isFirst = true;
775
776 for ( Rdn rdn : dn.getRdns() )
777 {
778
779 String normAT = rdn.getNormType();
780 AttributeType at = schemaManager.lookupAttributeTypeRegistry( normAT );
781
782 String atName = at.getName();
783
784
785 String normValue = rdn.getAva().getValue().getString();
786
787 if ( isFirst )
788 {
789 isFirst = false;
790 }
791 else
792 {
793 sb.append( "," );
794 }
795
796 sb.append( atName ).append( "=" ).append( normValue );
797 }
798
799 return getOSFileName( sb.toString() );
800 }
801
802
803
804
805
806
807
808 private String getOSFileName( String fileName )
809 {
810 StringBuilder sb = new StringBuilder();
811
812 for ( char c : fileName.toCharArray() )
813 {
814 switch ( c )
815 {
816 case 0x00:
817 case 0x01:
818 case 0x02:
819 case 0x03:
820 case 0x04:
821 case 0x05:
822 case 0x06:
823 case 0x07:
824 case 0x08:
825 case 0x09:
826 case 0x0A:
827 case 0x0B:
828 case 0x0C:
829 case 0x0D:
830 case 0x0E:
831 case 0x0F:
832 case 0x10:
833 case 0x11:
834 case 0x12:
835 case 0x13:
836 case 0x14:
837 case 0x15:
838 case 0x16:
839 case 0x17:
840 case 0x18:
841 case 0x19:
842 case 0x1A:
843 case 0x1B:
844 case 0x1C:
845 case 0x1D:
846 case 0x1E:
847 case 0x1F:
848 case 0x7F:
849 case ' ':
850 case '"':
851 case '%':
852 case '&':
853 case '(':
854 case ')':
855 case '*':
856 case '+':
857 case '/':
858 case ':':
859 case ';':
860 case '<':
861 case '>':
862 case '?':
863 case '[':
864 case '\\':
865 case ']':
866 case '|':
867 sb.append( "%" ).append( Strings.dumpHex( ( byte ) ( c >> 4 ) ) )
868 .append( Strings.dumpHex( ( byte ) ( c & 0xF ) ) );
869 break;
870
871 default:
872 sb.append( c );
873 break;
874 }
875 }
876
877 return Strings.toLowerCaseAscii( sb.toString() );
878 }
879
880
881
882
883
884
885 private void addEntry( Entry entry ) throws LdapException
886 {
887
888 entry.removeAttributes( entryDnAT );
889
890 try ( Writer fw = Files.newBufferedWriter( getFile( entry.getDn(), CREATE ).toPath(), StandardCharsets.UTF_8 ) )
891 {
892 fw.write( LdifUtils.convertToLdif( entry ) );
893 }
894 catch ( IOException ioe )
895 {
896 throw new LdapOperationException( ioe.getMessage(), ioe );
897 }
898 }
899
900
901
902
903
904
905
906 private boolean deleteFile( File file )
907 {
908 if ( file.isDirectory() )
909 {
910 File[] files = file.listFiles();
911
912
913 for ( File f : files )
914 {
915 deleteFile( f );
916 }
917
918
919 return file.delete();
920 }
921 else
922 {
923 return file.delete();
924 }
925 }
926 }