1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.ldap.server.partition.impl.btree.jdbm;
18
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.math.BigInteger;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27
28 import javax.naming.Name;
29 import javax.naming.NamingEnumeration;
30 import javax.naming.NamingException;
31 import javax.naming.directory.Attribute;
32 import javax.naming.directory.Attributes;
33 import javax.naming.directory.DirContext;
34 import javax.naming.directory.ModificationItem;
35
36 import jdbm.RecordManager;
37 import jdbm.helper.MRU;
38 import jdbm.recman.BaseRecordManager;
39 import jdbm.recman.CacheRecordManager;
40
41 import org.apache.ldap.common.exception.LdapNameNotFoundException;
42 import org.apache.ldap.common.exception.LdapSchemaViolationException;
43 import org.apache.ldap.common.message.LockableAttributeImpl;
44 import org.apache.ldap.common.message.LockableAttributesImpl;
45 import org.apache.ldap.common.message.ResultCodeEnum;
46 import org.apache.ldap.common.name.LdapName;
47 import org.apache.ldap.common.schema.AttributeType;
48 import org.apache.ldap.common.schema.Normalizer;
49 import org.apache.ldap.common.util.NamespaceTools;
50 import org.apache.ldap.server.DirectoryServiceConfiguration;
51 import org.apache.ldap.server.configuration.DirectoryPartitionConfiguration;
52 import org.apache.ldap.server.partition.DirectoryPartition;
53 import org.apache.ldap.server.partition.impl.btree.BTreeDirectoryPartition;
54 import org.apache.ldap.server.partition.impl.btree.Index;
55 import org.apache.ldap.server.partition.impl.btree.IndexAssertion;
56 import org.apache.ldap.server.partition.impl.btree.IndexAssertionEnumeration;
57 import org.apache.ldap.server.partition.impl.btree.IndexNotFoundException;
58 import org.apache.ldap.server.partition.impl.btree.IndexRecord;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62
63 /***
64 * A {@link DirectoryPartition} that stores entries in
65 * <a href="http://jdbm.sourceforge.net/">JDBM</a> database.
66 *
67 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68 * @version $Rev: 307234 $
69 */
70 public class JdbmDirectoryPartition extends BTreeDirectoryPartition
71 {
72 private static final Logger log = LoggerFactory.getLogger( JdbmDirectoryPartition.class );
73
74 /*** the JDBM record manager used by this database */
75 private RecordManager recMan;
76 /*** the user provided suffix of this backend database */
77 private Name upSuffix;
78 /*** the normalized suffix of this backend database */
79 private Name normSuffix;
80 /*** the working directory to use for files */
81 private File workingDirectory;
82 /*** the master table storing entries by primary key */
83 private JdbmMasterTable master;
84 /*** a map of attribute names to user indices */
85 private Map indices;
86 /*** a map of index names to system indices */
87 private Map sysIndices;
88
89 /*** true if open */
90 private boolean initialized;
91
92 /*** the normalized distinguished name index */
93 private Index ndnIdx;
94 /*** the user provided distinguished name index */
95 private Index updnIdx;
96 /*** the attribute existance index */
97 private Index existanceIdx;
98 /*** the parent child relationship index */
99 private Index hierarchyIdx;
100 /*** the one level scope alias index */
101 private Index oneAliasIdx;
102 /*** the subtree scope alias index */
103 private Index subAliasIdx;
104 /*** a system index on aliasedObjectName attribute */
105 private Index aliasIdx;
106
107
108
109
110
111
112
113 /***
114 * Creates a store based on JDBM B+Trees.
115 */
116 public JdbmDirectoryPartition()
117 {
118 }
119
120 public synchronized void init( DirectoryServiceConfiguration factoryCfg, DirectoryPartitionConfiguration cfg ) throws NamingException
121 {
122 this.upSuffix = new LdapName( cfg.getSuffix() );
123 this.normSuffix = cfg.getNormalizedSuffix( factoryCfg.getGlobalRegistries().getMatchingRuleRegistry() );
124
125 File workingDirectory = new File(
126 factoryCfg.getStartupConfiguration().getWorkingDirectory().getPath() +
127 File.separator + cfg.getName() );
128
129 workingDirectory.mkdirs();
130
131 this.workingDirectory = workingDirectory;
132
133 try
134 {
135 String path = workingDirectory.getPath() + File.separator + "master";
136 BaseRecordManager base = new BaseRecordManager( path );
137 base.disableTransactions();
138 recMan = new CacheRecordManager( base, new MRU( 1000 ) );
139 }
140 catch ( IOException e )
141 {
142 NamingException ne = new NamingException(
143 "Could not initialize RecordManager" );
144 ne.setRootCause( e );
145 throw ne;
146 }
147
148 master = new JdbmMasterTable( recMan );
149 indices = new HashMap();
150 sysIndices = new HashMap();
151
152 super.init( factoryCfg, cfg );
153 initialized = true;
154 }
155
156
157 public synchronized void destroy()
158 {
159 if ( !initialized )
160 {
161 return;
162 }
163
164 ArrayList array = new ArrayList();
165 array.addAll( indices.values() );
166
167 if ( null != ndnIdx )
168 {
169 array.add( ndnIdx );
170 }
171
172 if ( null != updnIdx )
173 {
174 array.add( updnIdx );
175 }
176
177 if ( null != aliasIdx )
178 {
179 array.add( aliasIdx );
180 }
181
182 if ( null != oneAliasIdx )
183 {
184 array.add( oneAliasIdx );
185 }
186
187 if ( null != subAliasIdx )
188 {
189 array.add( subAliasIdx );
190 }
191
192 if ( null != hierarchyIdx )
193 {
194 array.add( hierarchyIdx );
195 }
196
197 if ( null != existanceIdx )
198 {
199 array.add( existanceIdx );
200 }
201
202 Iterator list = array.iterator();
203
204 while ( list.hasNext() )
205 {
206 Index index = ( Index ) list.next();
207
208 try
209 {
210 index.close();
211 }
212 catch ( Throwable t )
213 {
214 log.error( "Failed to close an index.", t );
215 }
216 }
217
218 try
219 {
220 master.close();
221 }
222 catch ( Throwable t )
223 {
224 log.error( "Failed to close the master.", t );
225 }
226
227 try
228 {
229 recMan.close();
230 }
231 catch ( Throwable t )
232 {
233 log.error( "Failed to close the record manager", t );
234 }
235
236 initialized = false;
237 }
238
239
240 public boolean isInitialized()
241 {
242 return initialized;
243 }
244
245
246 public synchronized void sync() throws NamingException
247 {
248 if( !initialized )
249 {
250 return;
251 }
252
253 ArrayList array = new ArrayList();
254 array.addAll( indices.values() );
255 array.add( ndnIdx );
256 array.add( updnIdx );
257 array.add( aliasIdx );
258 array.add( oneAliasIdx );
259 array.add( subAliasIdx );
260 array.add( hierarchyIdx );
261 array.add( existanceIdx );
262
263 Iterator list = array.iterator();
264
265
266 while ( list.hasNext() )
267 {
268 Index idx = ( Index ) list.next();
269
270 idx.sync();
271 }
272
273 master.sync();
274
275 try
276 {
277 recMan.commit();
278 }
279 catch ( Throwable t )
280 {
281 throw ( NamingException ) new NamingException(
282 "Failed to commit changes to the record manager." ).initCause( t );
283 }
284 }
285
286
287
288
289
290
291
292 public void addIndexOn( AttributeType spec ) throws NamingException
293 {
294 Index idx = new JdbmIndex( spec, workingDirectory );
295 indices.put( spec.getName().toLowerCase(), idx );
296 }
297
298
299 public Index getExistanceIndex()
300 {
301 return existanceIdx;
302 }
303
304
305 public void setExistanceIndexOn( AttributeType attrType ) throws NamingException
306 {
307 if ( existanceIdx != null )
308 {
309 NamingException e = new NamingException( "Index already set!" );
310 throw e;
311 }
312
313 existanceIdx = new JdbmIndex( attrType, workingDirectory );
314 sysIndices.put( attrType.getName().toLowerCase(), existanceIdx );
315 }
316
317
318 public Index getHierarchyIndex()
319 {
320 return hierarchyIdx;
321 }
322
323
324 public void setHierarchyIndexOn( AttributeType attrType ) throws NamingException
325 {
326 if ( hierarchyIdx != null )
327 {
328 NamingException e = new NamingException( "Index already set!" );
329 throw e;
330 }
331
332 hierarchyIdx = new JdbmIndex( attrType, workingDirectory );
333 sysIndices.put( attrType.getName().toLowerCase(), hierarchyIdx );
334 }
335
336
337 public Index getAliasIndex()
338 {
339 return aliasIdx;
340 }
341
342
343 public void setAliasIndexOn( AttributeType attrType ) throws NamingException
344 {
345 if ( aliasIdx != null )
346 {
347 NamingException e = new NamingException( "Index already set!" );
348 throw e;
349 }
350
351 aliasIdx = new JdbmIndex( attrType, workingDirectory );
352 sysIndices.put( attrType.getName().toLowerCase(), aliasIdx );
353 }
354
355
356 public Index getOneAliasIndex()
357 {
358 return oneAliasIdx;
359 }
360
361
362 public void setOneAliasIndexOn( AttributeType attrType ) throws NamingException
363 {
364 if ( oneAliasIdx != null )
365 {
366 NamingException e = new NamingException( "Index already set!" );
367 throw e;
368 }
369
370 oneAliasIdx = new JdbmIndex( attrType, workingDirectory );
371 sysIndices.put( attrType.getName().toLowerCase(), oneAliasIdx );
372 }
373
374
375 public Index getSubAliasIndex()
376 {
377 return subAliasIdx;
378 }
379
380
381 public void setSubAliasIndexOn( AttributeType attrType ) throws NamingException
382 {
383 if ( subAliasIdx != null )
384 {
385 NamingException e = new NamingException( "Index already set!" );
386 throw e;
387 }
388
389 subAliasIdx = new JdbmIndex( attrType, workingDirectory );
390 sysIndices.put( attrType.getName().toLowerCase(), subAliasIdx );
391 }
392
393
394 public Index getUpdnIndex()
395 {
396 return updnIdx;
397 }
398
399
400 public void setUpdnIndexOn( AttributeType attrType ) throws NamingException
401 {
402 if ( updnIdx != null )
403 {
404 NamingException e = new NamingException( "Index already set!" );
405 throw e;
406 }
407
408 updnIdx = new JdbmIndex( attrType, workingDirectory );
409 sysIndices.put( attrType.getName().toLowerCase(), updnIdx );
410 }
411
412
413 public Index getNdnIndex()
414 {
415 return ndnIdx;
416 }
417
418
419 public void setNdnIndexOn( AttributeType attrType ) throws NamingException
420 {
421 if ( ndnIdx != null )
422 {
423 NamingException e = new NamingException( "Index already set!" );
424 throw e;
425 }
426
427 ndnIdx = new JdbmIndex( attrType, workingDirectory );
428 sysIndices.put( attrType.getName().toLowerCase(), ndnIdx );
429 }
430
431
432 public Iterator getUserIndices()
433 {
434 return indices.keySet().iterator();
435 }
436
437
438 public Iterator getSystemIndices()
439 {
440 return sysIndices.keySet().iterator();
441 }
442
443
444 public boolean hasUserIndexOn( String attribute )
445 {
446 return indices.containsKey( attribute ) ||
447 indices.containsKey( attribute.toLowerCase() );
448 }
449
450
451 public boolean hasSystemIndexOn( String attribute )
452 {
453 return sysIndices.containsKey( attribute ) ||
454 sysIndices.containsKey( attribute.toLowerCase() );
455 }
456
457
458 /***
459 * @todo replace lookups to use the OID instead of the name. Also note
460 * that the OID registry can be used to go between names and oids.
461 *
462 * @see org.apache.ldap.server.partition.impl.btree.BTreeDirectoryPartition#getUserIndex(String)
463 */
464 public Index getUserIndex( String attribute ) throws IndexNotFoundException
465 {
466 String lowerCased = attribute.toLowerCase();
467
468 if ( indices.containsKey( attribute ) )
469 {
470 return ( Index ) indices.get( attribute );
471 }
472 else if ( indices.containsKey( lowerCased ) )
473 {
474 return ( Index ) indices.get( lowerCased );
475 }
476 else
477 {
478 throw new IndexNotFoundException( "An index on attribute " +
479 attribute + " does not exist!" );
480 }
481 }
482
483
484 /***
485 * @todo replace lookups to use the OID instead of the name. Also note
486 * that the OID registry can be used to go between names and oids.
487 *
488 * @see BTreeDirectoryPartition#getEntryId(String)
489 */
490 public Index getSystemIndex( String indexName ) throws IndexNotFoundException
491 {
492 String lowerCased = indexName.toLowerCase();
493
494 if ( sysIndices.containsKey( indexName ) )
495 {
496 return ( Index ) sysIndices.get( indexName );
497 }
498 else if ( sysIndices.containsKey( lowerCased ) )
499 {
500 return ( Index ) sysIndices.get( lowerCased );
501 }
502 else
503 {
504 throw new IndexNotFoundException( "A system index by the name of " +
505 indexName + " does not exist!" );
506 }
507 }
508
509
510 public BigInteger getEntryId( String dn ) throws NamingException
511 {
512 return ndnIdx.forwardLookup( dn );
513 }
514
515
516 public String getEntryDn( BigInteger id ) throws NamingException
517 {
518 return ( String ) ndnIdx.reverseLookup( id );
519 }
520
521
522 public BigInteger getParentId( String dn ) throws NamingException
523 {
524 BigInteger childId = ndnIdx.forwardLookup( dn );
525 return ( BigInteger ) hierarchyIdx.reverseLookup( childId );
526 }
527
528
529 public BigInteger getParentId( BigInteger childId ) throws NamingException
530 {
531 return ( BigInteger ) hierarchyIdx.reverseLookup( childId );
532 }
533
534
535 public String getEntryUpdn( BigInteger id ) throws NamingException
536 {
537 return ( String ) updnIdx.reverseLookup( id );
538 }
539
540
541 public String getEntryUpdn( String dn ) throws NamingException
542 {
543 BigInteger id = ndnIdx.forwardLookup( dn );
544 return ( String ) updnIdx.reverseLookup( id );
545 }
546
547
548 public int count() throws NamingException
549 {
550 return master.count();
551 }
552
553
554 /***
555 * Removes the index entries for an alias before the entry is deleted from
556 * the master table.
557 *
558 * @todo Optimize this by walking the hierarchy index instead of the name
559 * @param aliasId the id of the alias entry in the master table
560 * @throws NamingException if we cannot delete the indices
561 */
562 private void dropAliasIndices( BigInteger aliasId ) throws NamingException
563 {
564 String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
565 BigInteger targetId = getEntryId( targetDn );
566 String aliasDn = getEntryDn( aliasId );
567 Name ancestorDn = new LdapName( aliasDn ).getPrefix( 1 );
568 BigInteger ancestorId = getEntryId( ancestorDn.toString() );
569
570
571
572
573
574
575
576
577
578
579
580
581 oneAliasIdx.drop( ancestorId, targetId );
582 subAliasIdx.drop( ancestorId, targetId );
583
584 while ( ! ancestorDn.equals( upSuffix ) )
585 {
586 ancestorDn = ancestorDn.getPrefix( 1 );
587 ancestorId = getEntryId( ancestorDn.toString() );
588
589 subAliasIdx.drop( ancestorId, targetId );
590 }
591
592
593 aliasIdx.drop( aliasId );
594 }
595
596
597 /***
598 * Adds indices for an aliasEntry to be added to the database while checking
599 * for constrained alias constructs like alias cycles and chaining.
600 *
601 * @param aliasDn normalized distinguished name for the alias entry
602 * @param aliasTarget the user provided aliased entry dn as a string
603 * @param aliasId the id of alias entry to add
604 * @throws NamingException if index addition fails, of the alias is not
605 * allowed due to chaining or cycle formation.
606 */
607 private void addAliasIndices( BigInteger aliasId, Name aliasDn,
608 String aliasTarget ) throws NamingException
609 {
610 Name targetDn = null;
611 BigInteger targetId = null;
612 Normalizer normalizer = null;
613 Name ancestorDn = null;
614 BigInteger ancestorId = null;
615
616
617 normalizer = oneAliasIdx.getAttribute().getEquality().getNormalizer();
618 targetDn = new LdapName( ( String ) normalizer.normalize( aliasTarget ) );
619
620
621
622
623
624
625
626
627
628
629
630 if ( aliasDn.startsWith( targetDn ) )
631 {
632 if ( aliasDn.equals( targetDn ) )
633 {
634 throw new NamingException( "[36] aliasDereferencingProblem - "
635 + "attempt to create alias to itself." );
636 }
637
638 throw new NamingException( "[36] aliasDereferencingProblem - "
639 + "attempt to create alias with cycle to relative "
640 + aliasTarget + " not allowed from descendent alias "
641 + aliasDn );
642 }
643
644
645
646
647
648
649
650
651
652 if ( ! targetDn.startsWith( upSuffix ) )
653 {
654
655 throw new NamingException( "[36] aliasDereferencingProblem -"
656 + " the alias points to an entry outside of the " + upSuffix
657 + " namingContext to an object whose existance cannot be"
658 + " determined." );
659 }
660
661
662 targetId = ndnIdx.forwardLookup( targetDn.toString() );
663
664
665
666
667
668
669
670 if ( null == targetId )
671 {
672
673 throw new NamingException( "[33] aliasProblem - "
674 + "the alias when dereferenced would not name a known object "
675 + "the aliasedObjectName must be set to a valid existing "
676 + "entry." );
677 }
678
679
680
681
682
683
684
685
686
687
688
689 if ( null != aliasIdx.reverseLookup( targetId ) )
690 {
691
692 throw new NamingException( "[36] aliasDereferencingProblem -"
693 + " the alias points to another alias. Alias chaining is"
694 + " not supported by this backend." );
695 }
696
697
698 aliasIdx.add( aliasTarget, aliasId );
699
700
701
702
703
704
705
706
707 ancestorDn = aliasDn.getPrefix( 1 );
708 ancestorId = getEntryId( ancestorDn.toString() );
709
710 if ( ! NamespaceTools.isSibling( targetDn, aliasDn ) )
711 {
712 oneAliasIdx.add( ancestorId, targetId );
713 }
714
715
716
717
718
719
720
721
722
723
724
725 while ( ! ancestorDn.equals( upSuffix ) && null != ancestorId )
726 {
727 if ( ! NamespaceTools.isDescendant( ancestorDn, targetDn ) )
728 {
729 subAliasIdx.add( ancestorId, targetId );
730 }
731
732 ancestorDn = ancestorDn.getPrefix( 1 );
733 ancestorId = getEntryId( ancestorDn.toString() );
734 }
735 }
736
737
738 public void add( String updn, Name dn, Attributes entry ) throws NamingException
739 {
740 BigInteger id;
741 BigInteger parentId = null;
742
743 id = master.getNextId();
744
745
746
747
748
749
750
751 if ( dn.equals( normSuffix ) )
752 {
753 parentId = BigInteger.ZERO;
754 }
755 else
756 {
757 parentId = getEntryId( dn.getPrefix( 1 ).toString() );
758 }
759
760
761 if ( parentId == null )
762 {
763 throw new LdapNameNotFoundException( "Id for parent '" + dn.getPrefix( 1 ).toString() + "' not found!" );
764 }
765
766 Attribute objectClass = entry.get( "objectClass" );
767
768 if ( objectClass == null )
769 {
770 String msg = "Entry " + updn + " contains no objectClass attribute: " + entry;
771
772 throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSVIOLATION );
773 }
774
775
776
777
778 if ( entry.get( "objectClass" ).contains( DirectoryPartition.ALIAS_OBJECT ) )
779 {
780 addAliasIndices( id, dn, ( String ) entry.get( DirectoryPartition.ALIAS_ATTRIBUTE ).get() );
781 }
782
783 ndnIdx.add( dn.toString(), id );
784 updnIdx.add( updn, id );
785 hierarchyIdx.add( parentId, id );
786
787
788 NamingEnumeration list = entry.getIDs();
789 while ( list.hasMore() )
790 {
791 String attribute = ( String ) list.next();
792
793 if ( hasUserIndexOn( attribute ) )
794 {
795 Index idx = getUserIndex( attribute );
796 NamingEnumeration values = entry.get( attribute ).getAll();
797
798 while ( values.hasMore() )
799 {
800 idx.add( values.next(), id );
801 }
802
803
804 existanceIdx.add( attribute.toLowerCase(), id );
805 }
806 }
807
808 master.put( entry, id );
809 }
810
811
812 public Attributes lookup( BigInteger id ) throws NamingException
813 {
814 return master.get( id );
815 }
816
817
818 public void delete( BigInteger id ) throws NamingException
819 {
820 Attributes entry = lookup( id );
821 BigInteger parentId = getParentId( id );
822 NamingEnumeration attrs = entry.getIDs();
823
824 if ( entry.get( "objectClass" ).contains( DirectoryPartition.ALIAS_OBJECT ) )
825 {
826 dropAliasIndices( id );
827 }
828
829 ndnIdx.drop( id );
830 updnIdx.drop( id );
831 hierarchyIdx.drop( id );
832
833
834 if ( ! parentId.equals( BigInteger.ZERO ) )
835 {
836 hierarchyIdx.drop( parentId, id );
837 }
838
839 while ( attrs.hasMore() )
840 {
841 String attr = ( ( String ) attrs.next() );
842
843 if ( hasUserIndexOn( attr ) )
844 {
845 Index index = getUserIndex( attr );
846 NamingEnumeration values = entry.get( attr ).getAll();
847
848 while ( values.hasMore() )
849 {
850 index.drop( values.next(), id );
851 }
852
853 existanceIdx.drop( attr.toLowerCase(), id );
854 }
855 }
856
857 master.delete( id );
858 }
859
860
861 public NamingEnumeration list( BigInteger id ) throws NamingException
862 {
863 return hierarchyIdx.listIndices( id );
864 }
865
866
867 public int getChildCount( BigInteger id ) throws NamingException
868 {
869 return hierarchyIdx.count( id );
870 }
871
872
873 public Name getSuffix( boolean normalized )
874 {
875 if ( normalized )
876 {
877 return normSuffix;
878 }
879
880 return upSuffix;
881 }
882
883
884 public Attributes getSuffixEntry() throws NamingException
885 {
886 BigInteger id = getEntryId( upSuffix.toString() );
887
888 if ( null == id )
889 {
890 return null;
891 }
892
893 return lookup( id );
894 }
895
896
897 public void setProperty( String propertyName, String propertyValue )
898 throws NamingException
899 {
900 master.setProperty( propertyName, propertyValue );
901 }
902
903
904 public String getProperty( String propertyName ) throws NamingException
905 {
906 return master.getProperty( propertyName );
907 }
908
909
910 public Attributes getIndices( BigInteger id ) throws NamingException
911 {
912 Attributes attributes = new LockableAttributesImpl();
913
914
915 attributes.put( "_nDn", getEntryDn( id ) );
916 attributes.put( "_upDn", getEntryUpdn( id ) );
917 attributes.put( "_parent", getParentId( id ) );
918
919
920 Iterator idxList = this.indices.values().iterator();
921 while ( idxList.hasNext() )
922 {
923 Index index = ( Index ) idxList.next();
924 NamingEnumeration list = index.listReverseIndices( id );
925 while ( list.hasMore() )
926 {
927 IndexRecord rec = ( IndexRecord ) list.next();
928 Object val = rec.getIndexKey();
929 String attrId = index.getAttribute().getName();
930 Attribute attr = attributes.get( attrId );
931 if ( attr == null)
932 {
933 attr = new LockableAttributeImpl( attrId );
934 }
935 attr.add( val );
936 attributes.put( attr );
937 }
938 }
939
940
941
942 NamingEnumeration list = existanceIdx.listReverseIndices( id );
943 StringBuffer val = new StringBuffer();
944 while ( list.hasMore() )
945 {
946 IndexRecord rec = ( IndexRecord ) list.next();
947 val.append( "_existance[" );
948 val.append( rec.getIndexKey() );
949 val.append( "]" );
950
951 String valStr = val.toString();
952 Attribute attr = attributes.get( valStr );
953 if ( attr == null )
954 {
955 attr = new LockableAttributeImpl( valStr );
956 }
957 attr.add( rec.getEntryId() );
958 attributes.put( attr );
959 val.setLength( 0 );
960 }
961
962
963
964 list = hierarchyIdx.listIndices( id );
965 Attribute childAttr = new LockableAttributeImpl( "_child" );
966 attributes.put( childAttr );
967 while ( list.hasMore() )
968 {
969 IndexRecord rec = ( IndexRecord ) list.next();
970 childAttr.add( rec.getEntryId() );
971 }
972
973 return attributes;
974 }
975
976
977 /***
978 * Adds a set of attribute values while affecting the appropriate indices.
979 * The entry is not persisted: it is only changed in anticipation for a put
980 * into the master table.
981 *
982 * @param id the primary key of the entry
983 * @param entry the entry to alter
984 * @param mods the attribute and values to add
985 * @throws NamingException if index alteration or attribute addition
986 * fails.
987 */
988 private void add( BigInteger id, Attributes entry, Attribute mods )
989 throws NamingException
990 {
991 if ( hasUserIndexOn( mods.getID() ) )
992 {
993 Index idx = getUserIndex( mods.getID() );
994 idx.add( mods, id );
995
996
997 if ( ! existanceIdx.hasValue( mods.getID().toLowerCase(), id ) )
998 {
999 existanceIdx.add( mods.getID().toLowerCase(), id );
1000 }
1001 }
1002
1003
1004 Attribute entryAttrToAddTo = entry.get( mods.getID() );
1005
1006 if ( entryAttrToAddTo == null )
1007 {
1008 entryAttrToAddTo = new LockableAttributeImpl( mods.getID() );
1009 entry.put( entryAttrToAddTo );
1010 }
1011
1012 for ( int ii = 0; ii < mods.size(); ii++ )
1013 {
1014 entryAttrToAddTo.add( mods.get( ii ) );
1015 }
1016
1017 if ( mods.getID().equals( DirectoryPartition.ALIAS_ATTRIBUTE ) )
1018 {
1019 String ndnStr = ( String ) ndnIdx.reverseLookup( id );
1020 addAliasIndices( id, new LdapName( ndnStr ),
1021 ( String ) mods.get() );
1022 }
1023 }
1024
1025
1026 /***
1027 * Completely removes the set of values for an attribute having the values
1028 * supplied while affecting the appropriate indices. The entry is not
1029 * persisted: it is only changed in anticipation for a put into the master
1030 * table. Note that an empty attribute w/o values will remove all the
1031 * values within the entry where as an attribute w/ values will remove those
1032 * attribute values it contains.
1033 *
1034 * @param id the primary key of the entry
1035 * @param entry the entry to alter
1036 * @param mods the attribute and its values to delete
1037 * @throws NamingException if index alteration or attribute modification
1038 * fails.
1039 */
1040 private void remove( BigInteger id, Attributes entry, Attribute mods )
1041 throws NamingException
1042 {
1043 if ( hasUserIndexOn( mods.getID() ) )
1044 {
1045 Index idx = getUserIndex( mods.getID() );
1046 idx.drop( mods, id );
1047
1048
1049
1050
1051
1052 if ( null == idx.reverseLookup( id ) )
1053 {
1054 existanceIdx.drop( mods.getID(), id );
1055 }
1056 }
1057
1058
1059
1060
1061
1062
1063
1064 if ( mods.size() == 0 )
1065 {
1066 entry.remove( mods.getID() );
1067 }
1068 else
1069 {
1070 Attribute entryAttr = entry.get( mods.getID() );
1071 NamingEnumeration values = mods.getAll();
1072 while ( values.hasMore() )
1073 {
1074 entryAttr.remove( values.next() );
1075 }
1076 }
1077
1078
1079 if ( mods.getID().equals( DirectoryPartition.ALIAS_ATTRIBUTE ) )
1080 {
1081 dropAliasIndices( id );
1082 }
1083 }
1084
1085
1086 /***
1087 * Completely replaces the existing set of values for an attribute with the
1088 * modified values supplied affecting the appropriate indices. The entry
1089 * is not persisted: it is only changed in anticipation for a put into the
1090 * master table.
1091 *
1092 * @param id the primary key of the entry
1093 * @param entry the entry to alter
1094 * @param mods the replacement attribute and values
1095 * @throws NamingException if index alteration or attribute modification
1096 * fails.
1097 */
1098 private void replace( BigInteger id, Attributes entry, Attribute mods )
1099 throws NamingException
1100 {
1101 if ( hasUserIndexOn( mods.getID() ) )
1102 {
1103 Index idx = getUserIndex( mods.getID() );
1104
1105
1106 idx.drop( id );
1107 idx.add( mods, id );
1108
1109
1110
1111
1112
1113 if ( null == idx.reverseLookup( id ) )
1114 {
1115 existanceIdx.drop( mods.getID(), id );
1116 }
1117 }
1118
1119 if ( mods.getID().equals( DirectoryPartition.ALIAS_ATTRIBUTE ) )
1120 {
1121 dropAliasIndices( id );
1122 }
1123
1124
1125 entry.put( mods );
1126
1127 if ( mods.getID().equals( DirectoryPartition.ALIAS_ATTRIBUTE ) )
1128 {
1129 String ndnStr = ( String ) ndnIdx.reverseLookup( id );
1130 addAliasIndices( id, new LdapName( ndnStr ),
1131 ( String ) mods.get() );
1132 }
1133 }
1134
1135
1136 public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
1137 {
1138 NamingEnumeration attrs = null;
1139 BigInteger id = getEntryId( dn.toString() );
1140 Attributes entry = master.get( id );
1141
1142 switch ( modOp )
1143 {
1144 case( DirContext.ADD_ATTRIBUTE ):
1145 attrs = mods.getIDs();
1146
1147 while ( attrs.hasMore() )
1148 {
1149 String attrId = ( String ) attrs.next();
1150 Attribute attr = mods.get( attrId );
1151 add( id, entry, attr );
1152 }
1153
1154 break;
1155 case( DirContext.REMOVE_ATTRIBUTE ):
1156 attrs = mods.getIDs();
1157
1158 while ( attrs.hasMore() )
1159 {
1160 String attrId = ( String ) attrs.next();
1161 Attribute attr = mods.get( attrId );
1162 remove( id, entry, attr );
1163 }
1164
1165 break;
1166 case( DirContext.REPLACE_ATTRIBUTE ):
1167 attrs = mods.getIDs();
1168
1169 while ( attrs.hasMore() )
1170 {
1171 String attrId = ( String ) attrs.next();
1172 Attribute attr = mods.get( attrId );
1173 replace( id, entry, attr );
1174 }
1175
1176 break;
1177 default:
1178 throw new NamingException(
1179 "Unidentified modification operation" );
1180 }
1181
1182 master.put( entry, id );
1183 }
1184
1185
1186 public void modify( Name dn, ModificationItem [] mods ) throws NamingException
1187 {
1188 BigInteger id = getEntryId( dn.toString() );
1189 Attributes entry = master.get( id );
1190
1191 for ( int ii = 0; ii < mods.length; ii++ )
1192 {
1193 Attribute attrMods = mods[ii].getAttribute();
1194
1195 switch ( mods[ ii ].getModificationOp() )
1196 {
1197 case( DirContext.ADD_ATTRIBUTE ):
1198 add( id, entry, attrMods );
1199 break;
1200 case( DirContext.REMOVE_ATTRIBUTE ):
1201 remove( id, entry, attrMods );
1202 break;
1203 case( DirContext.REPLACE_ATTRIBUTE ):
1204 replace( id, entry, attrMods );
1205 break;
1206 default:
1207 throw new NamingException(
1208 "Unidentified modification operation" );
1209 }
1210 }
1211
1212 master.put( entry, id );
1213 }
1214
1215
1216 /***
1217 * Changes the relative distinuished name of an entry specified by a
1218 * distinguished name with the optional removal of the old Rdn attribute
1219 * value from the entry. Name changes propagate down as dn changes to the
1220 * descendants of the entry where the Rdn changed.
1221 *
1222 * An Rdn change operation does not change parent child relationships. It
1223 * merely propagates a name change at a point in the DIT where the Rdn is
1224 * changed. The change propagates down the subtree rooted at the
1225 * distinguished name specified.
1226 *
1227 * @param dn the normalized distinguished name of the entry to alter
1228 * @param newRdn the new Rdn to set
1229 * @param deleteOldRdn whether or not to remove the old Rdn attr/val
1230 * @throws NamingException if there are any errors propagating the name
1231 * changes.
1232 */
1233 public void modifyRn( Name dn, String newRdn, boolean deleteOldRdn )
1234 throws NamingException
1235 {
1236 String newRdnAttr = NamespaceTools.getRdnAttribute( newRdn );
1237 String newRdnValue = NamespaceTools.getRdnValue( newRdn );
1238 BigInteger id = getEntryId( dn.toString() );
1239 Attributes entry = lookup( id );
1240 Name updn = new LdapName( getEntryUpdn( id ) );
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251 Attribute rdnAttr = entry.get( newRdnAttr );
1252 if ( rdnAttr == null )
1253 {
1254 rdnAttr = new LockableAttributeImpl( newRdnAttr );
1255 }
1256
1257
1258 if ( ! rdnAttr.contains( newRdnValue ) )
1259 {
1260 rdnAttr.add( newRdnValue );
1261 }
1262
1263 entry.put( rdnAttr );
1264
1265 if ( hasUserIndexOn( newRdnAttr ) )
1266 {
1267 Index idx = getUserIndex( newRdnAttr );
1268 idx.add( newRdnValue, id );
1269
1270
1271 if ( ! existanceIdx.hasValue( newRdnAttr.toLowerCase(), id ) )
1272 {
1273 existanceIdx.add( newRdnAttr.toLowerCase(), id );
1274 }
1275 }
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291 if ( deleteOldRdn )
1292 {
1293 String oldRdn = updn.get( updn.size() - 1 );
1294 String oldRdnAttr = NamespaceTools.getRdnAttribute( oldRdn );
1295 String oldRdnValue = NamespaceTools.getRdnValue( oldRdn );
1296
1297 entry.get( oldRdnAttr ).remove( oldRdnValue );
1298
1299 if ( hasUserIndexOn( oldRdnAttr ) )
1300 {
1301 Index idx = getUserIndex( oldRdnAttr );
1302 idx.drop( oldRdnValue, id );
1303
1304
1305
1306
1307
1308 if ( null == idx.reverseLookup( id ) )
1309 {
1310 existanceIdx.drop( oldRdnAttr, id );
1311 }
1312 }
1313 }
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326 Name newUpdn = ( Name ) updn.clone();
1327 newUpdn.remove( newUpdn.size() - 1 );
1328 newUpdn.add( newUpdn.size(), newRdn );
1329 modifyDn( id, newUpdn, false );
1330 }
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340 /***
1341 * Recursively modifies the distinguished name of an entry and the names of
1342 * its descendants calling itself in the recursion.
1343 *
1344 * @param id the primary key of the entry
1345 * @param updn User provided distinguished name to set as the new DN
1346 * @param isMove whether or not the name change is due to a move operation
1347 * which affects alias indices.
1348 * @throws NamingException if something goes wrong
1349 */
1350 private void modifyDn( BigInteger id, Name updn, boolean isMove )
1351 throws NamingException
1352 {
1353 String aliasTarget = null;
1354
1355
1356 ndnIdx.drop( id );
1357 ndnIdx.add( ndnIdx.getNormalized( updn.toString() ), id );
1358
1359 updnIdx.drop( id );
1360 updnIdx.add( updn.toString(), id );
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372 if ( isMove )
1373 {
1374 aliasTarget = ( String ) aliasIdx.reverseLookup( id );
1375
1376 if ( null != aliasTarget )
1377 {
1378 addAliasIndices( id, new LdapName( getEntryDn( id ) ),
1379 aliasTarget );
1380 }
1381 }
1382
1383 NamingEnumeration children = list( id );
1384 while ( children.hasMore() )
1385 {
1386
1387 IndexRecord rec = ( IndexRecord ) children.next();
1388 BigInteger childId = rec.getEntryId();
1389
1390
1391
1392
1393
1394 Name childUpdn = ( Name ) updn.clone();
1395 Name oldUpdn = new LdapName( getEntryUpdn( childId ) );
1396 String rdn = LdapName.getRdn( oldUpdn );
1397 childUpdn.add( childUpdn.size(), rdn );
1398
1399
1400 modifyDn( childId, childUpdn, isMove );
1401 }
1402 }
1403
1404
1405 public void move( Name oldChildDn, Name newParentDn, String newRdn,
1406 boolean deleteOldRdn ) throws NamingException
1407 {
1408 BigInteger childId = getEntryId( oldChildDn.toString() );
1409 modifyRn( oldChildDn, newRdn, deleteOldRdn );
1410 move( oldChildDn, childId, newParentDn );
1411 }
1412
1413
1414 public void move( Name oldChildDn, Name newParentDn ) throws NamingException
1415 {
1416 BigInteger childId = getEntryId( oldChildDn.toString() );
1417 move( oldChildDn, childId, newParentDn );
1418 }
1419
1420
1421 /***
1422 * Moves an entry under a new parent. The operation causes a shift in the
1423 * parent child relationships between the old parent, new parent and the
1424 * child moved. All other descendant entries under the child never change
1425 * their direct parent child relationships. Hence after the parent child
1426 * relationship changes are broken at the old parent and set at the new
1427 * parent a modifyDn operation is conducted to handle name changes
1428 * propagating down through the moved child and its descendants.
1429 *
1430 * @param oldChildDn the normalized dn of the child to be moved
1431 * @param newParentDn the normalized dn of the new parent for the child
1432 * @throws NamingException if something goes wrong
1433 */
1434 private void move( Name oldChildDn, BigInteger childId, Name newParentDn ) throws NamingException
1435 {
1436
1437 BigInteger newParentId = getEntryId( newParentDn.toString() );
1438 BigInteger oldParentId = getParentId( childId );
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448 dropMovedAliasIndices( oldChildDn );
1449
1450
1451
1452
1453
1454 hierarchyIdx.drop( oldParentId, childId );
1455 hierarchyIdx.add( newParentId, childId );
1456
1457
1458
1459
1460
1461
1462 Name childUpdn = new LdapName( getEntryUpdn( childId ) );
1463 String childRdn = childUpdn.get( childUpdn.size() - 1 );
1464 Name newUpdn = new LdapName( getEntryUpdn( newParentId ) );
1465 newUpdn.add( newUpdn.size(), childRdn );
1466
1467
1468 modifyDn( childId, newUpdn, true );
1469 }
1470
1471
1472 /***
1473 * For all aliases including and under the moved base, this method removes
1474 * one and subtree alias index tuples for old ancestors above the moved base
1475 * that will no longer be ancestors after the move.
1476 *
1477 * @param movedBase the base at which the move occured - the moved node
1478 * @throws NamingException if system indices fail
1479 */
1480 private void dropMovedAliasIndices( final Name movedBase ) throws NamingException
1481 {
1482
1483 IndexAssertion isBaseDescendant = new IndexAssertion()
1484 {
1485 public boolean assertCandidate( IndexRecord rec )
1486 throws NamingException
1487 {
1488 String dn = getEntryDn( rec.getEntryId() );
1489 if ( dn.endsWith( movedBase.toString() ) )
1490 {
1491 return true;
1492 }
1493
1494 return false;
1495 }
1496 };
1497
1498 BigInteger movedBaseId = getEntryId( movedBase.toString() );
1499 if ( aliasIdx.reverseLookup( movedBaseId ) != null )
1500 {
1501 dropAliasIndices( movedBaseId, movedBase );
1502 }
1503
1504 NamingEnumeration aliases = new IndexAssertionEnumeration(
1505 aliasIdx.listIndices( movedBase.toString(), true ),
1506 isBaseDescendant );
1507 while ( aliases.hasMore() )
1508 {
1509 IndexRecord entry = ( IndexRecord ) aliases.next();
1510 dropAliasIndices( entry.getEntryId(), movedBase );
1511 }
1512 }
1513
1514
1515 /***
1516 * For the alias id all ancestor one and subtree alias tuples are moved
1517 * above the moved base.
1518 *
1519 * @param aliasId the id of the alias
1520 * @param movedBase the base where the move occured
1521 * @throws NamingException if indices fail
1522 */
1523 private void dropAliasIndices( BigInteger aliasId, Name movedBase )
1524 throws NamingException
1525 {
1526 String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
1527 BigInteger targetId = getEntryId( targetDn );
1528 String aliasDn = getEntryDn( aliasId );
1529
1530
1531
1532
1533
1534 Name ancestorDn = movedBase.getPrefix( 1 );
1535 BigInteger ancestorId = getEntryId( ancestorDn.toString() );
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549 if ( aliasDn.equals( movedBase.toString() ) )
1550 {
1551 oneAliasIdx.drop( ancestorId, targetId );
1552 }
1553
1554 subAliasIdx.drop( ancestorId, targetId );
1555
1556 while ( ! ancestorDn.equals( upSuffix ) )
1557 {
1558 ancestorDn = ancestorDn.getPrefix( 1 );
1559 ancestorId = getEntryId( ancestorDn.toString() );
1560
1561 subAliasIdx.drop( ancestorId, targetId );
1562 }
1563 }
1564 }
1565