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