View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
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     // C O N S T R U C T O R S
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         // Sync all user defined indices
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     // I N D E X   M E T H O D S
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          * We cannot just drop all tuples in the one level and subtree indices
617          * linking baseIds to the targetId.  If more than one alias refers to
618          * the target then droping all tuples with a value of targetId would
619          * make all other aliases to the target inconsistent.
620          * 
621          * We need to walk up the path of alias ancestors until we reach the 
622          * upSuffix, deleting each ( ancestorId, targetId ) tuple in the
623          * subtree scope alias.  We only need to do this for the direct parent
624          * of the alias on the one level subtree.
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         // Drops all alias tuples pointing to the id of the alias to be deleted
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;            // Name value of aliasedObjectName
656         BigInteger targetId = null;      // Id of the aliasedObjectName
657         Normalizer normalizer = null;    // Temporary handle for Dn's
658         Name ancestorDn = null;          // Name of an alias entry relative
659         BigInteger ancestorId = null;    // Id of an alias entry relative
660 
661         // Access aliasedObjectName, normalize it and generate the Name 
662         normalizer = oneAliasIdx.getAttribute().getEquality().getNormalizer();
663         targetDn = new LdapName( ( String ) normalizer.normalize( aliasTarget ) );
664            
665         /*
666          * Check For Cycles
667          * 
668          * Before wasting time to lookup more values we check using the target
669          * dn to see if we have the possible formation of an alias cycle.  This
670          * happens when the alias refers back to a target that is also a 
671          * relative of the alias entry.  For detection we test if the aliased
672          * entry Dn starts with the target Dn.  If it does then we know the 
673          * aliased target is a relative and we have a perspecitive cycle.
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          * Check For Aliases External To Naming Context
691          * 
692          * id may be null but the alias may be to a valid entry in 
693          * another namingContext.  Such aliases are not allowed and we
694          * need to point it out to the user instead of saying the target
695          * does not exist when it potentially could outside of this upSuffix.
696          */
697         if ( ! targetDn.startsWith( upSuffix ) )
698         {
699             // Complain specifically about aliases to outside naming contexts
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         // L O O K U P   T A R G E T   I D
707         targetId = ndnIdx.forwardLookup( targetDn.toString() );
708 
709         /*
710          * Check For Target Existance
711          * 
712          * We do not allow the creation of inconsistant aliases.  Aliases should
713          * not be broken links.  If the target does not exist we start screaming
714          */
715         if ( null == targetId )
716         {
717             // Complain about target not existing
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          * Detect Direct Alias Chain Creation
726          * 
727          * Rather than resusitate the target to test if it is an alias and fail
728          * due to chaing creation we use the alias index to determine if the
729          * target is an alias.  Hence if the alias we are about to create points
730          * to another alias as its target in the aliasedObjectName attribute, 
731          * then we have a situation where an alias chain is being created.  
732          * Alias chaining is not allowed so we throw and exception. 
733          */
734         if ( null != aliasIdx.reverseLookup( targetId ) )
735         {
736             // Complain about illegal alias chain
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         // Add the alias to the simple alias index
743         aliasIdx.add( aliasTarget, aliasId );
744         
745         /*
746          * Handle One Level Scope Alias Index
747          * 
748          * The first relative is special with respect to the one level alias
749          * index.  If the target is not a sibling of the alias then we add the
750          * index entry maping the parent's id to the aliased target id.
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          * Handle Sub Level Scope Alias Index
762          * 
763          * Walk the list of relatives from the parents up to the upSuffix, testing
764          * to see if the alias' target is a descendant of the relative.  If the
765          * alias target is not a descentant of the relative it extends the scope
766          * and is added to the sub tree scope alias index.  The upSuffix node is
767          * ignored since everything is under its scope.  The first loop 
768          * iteration shall handle the parents.
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         // Suffix entry cannot have a parent since it is the root so it is 
792         // capped off using the zero value which no entry can have since 
793         // entry sequences start at 1.
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         // don't keep going if we cannot find the parent Id
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         // Start adding the system indices
821         // Why bother doing a lookup if this is not an alias.
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         // Now work on the user defined indices
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                 // Adds only those attributes that are indexed
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         // Remove parent's reference to entry only if entry is not the upSuffix
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         // Get the distinguishedName to id mapping
960         attributes.put( "_nDn", getEntryDn( id ) );
961         attributes.put( "_upDn", getEntryUpdn( id ) );
962         attributes.put( "_parent", getParentId( id ) );
963 
964         // Get all standard index attribute to value mappings
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         // Get all existance mappings for this id creating a special key
986         // that looks like so 'existance[attribute]' and the value is set to id
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         // Get all parent child mappings for this entry as the parent using the
1008         // key 'child' with many entries following it.
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             // If the attr didn't exist for this id add it to existance index
1042             if ( ! existanceIdx.hasValue( mods.getID().toLowerCase(), id ) )
1043             {
1044                 existanceIdx.add( mods.getID().toLowerCase(), id );
1045             }
1046         }
1047 
1048         // add all the values in mods to the same attribute in the entry
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              * If no attribute values exist for this entryId in the index then
1095              * we remove the existance index entry for the removed attribute.
1096              */
1097             if ( null == idx.reverseLookup( id ) )
1098             {
1099                 existanceIdx.drop( mods.getID(), id );
1100             }
1101         }
1102 
1103         /*
1104          * If there are no attribute values in the modifications then this 
1105          * implies the compelete removal of the attribute from the entry. Else
1106          * we remove individual attribute values from the entry in mods one 
1107          * at a time.
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         // Aliases->single valued comp/partial attr removal is not relevant here
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             // Drop all existing attribute value index entries and add new ones
1151             idx.drop( id );
1152             idx.add( mods, id );
1153     
1154             /* 
1155              * If no attribute values exist for this entryId in the index then
1156              * we remove the existance index entry for the removed attribute.
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         // Automatically replaces old attributes with new modified ones
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          * H A N D L E   N E W   R D N
1289          * ====================================================================
1290          * Add the new Rdn attribute to the entry.  If an index exists on the 
1291          * new Rdn attribute we add the index for this attribute value pair.
1292          * Also we make sure that the existance index shows the existance of the
1293          * new Rdn attribute within this entry.
1294          */
1295 
1296         Attribute rdnAttr = entry.get( newRdnAttr );
1297         if ( rdnAttr == null )
1298         {
1299             rdnAttr = new LockableAttributeImpl( newRdnAttr );
1300         }
1301 
1302         // add the new Rdn value only if it is not already present in the entry
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             // Make sure the altered entry shows the existance of the new attrib
1316             if ( ! existanceIdx.hasValue( newRdnAttr.toLowerCase(), id ) )
1317             {
1318                 existanceIdx.add( newRdnAttr.toLowerCase(), id );
1319             }
1320         }
1321 
1322         /*
1323          * H A N D L E   O L D   R D N
1324          * ====================================================================
1325          * If the old Rdn is to be removed we need to get the attribute and 
1326          * value for it.  Keep in mind the old Rdn need not be based on the 
1327          * same Rdn as the new one.  We remove the Rdn value from the entry
1328          * and remove the value/id tuple from the index on the old Rdn attr
1329          * if any.  We also test if the delete of the old Rdn index tuple 
1330          * removed all the attribute values of the old Rdn using a reverse
1331          * lookup.  If so that means we blew away the last value of the old 
1332          * Rdn attribute.  In this case we need to remove the attrName/id 
1333          * tuple from the existance index.
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                  * If there is no value for id in this index due to our
1351                  * drop above we remove the oldRdnAttr from the existance idx
1352                  */
1353                 if ( null == idx.reverseLookup( id ) )
1354                 {
1355                     existanceIdx.drop( oldRdnAttr, id );
1356                 }
1357             }
1358         }
1359         
1360         /*
1361          * H A N D L E   D N   C H A N G E
1362          * ====================================================================
1363          * 1) Build the new user defined distinguished name
1364          *      - clone / copy old updn
1365          *      - remove old upRdn from copy
1366          *      - add the new upRdn to the copy
1367          * 2) Make call to recursive modifyDn method to change the names of the
1368          *    entry and its descendants
1369          */
1370 
1371         Name newUpdn = ( Name ) updn.clone();      // copy da old updn
1372         newUpdn.remove( newUpdn.size() - 1 );      // remove old upRdn
1373         newUpdn.add( newUpdn.size(), newRdn );   // add da new upRdn
1374         modifyDn( id, newUpdn, false );            // propagate dn changes
1375     }
1376     
1377     
1378     /*
1379      * The move operation severs a child from a parent creating a new parent
1380      * child relationship.  As a consequence the relationships between the 
1381      * old ancestors of the child and its descendants change.  A descendant is
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         // Now we can handle the appropriate name indices for all cases
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          * Read Alias Index Tuples
1409          * 
1410          * If this is a name change due to a move operation then the one and
1411          * subtree indices for aliases were purged before the aliases were 
1412          * moved.  Now we must add them for each alias entry we have moved.  
1413          * 
1414          * aliasTarget is used as a marker to tell us if we're moving an 
1415          * alias.  If it is null then the moved entry is not an alias.
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             // Get the child and its id
1432             IndexRecord rec = ( IndexRecord ) children.next();
1433             BigInteger childId = rec.getEntryId();
1434             
1435             /* 
1436              * Calculate the Dn for the child's new name by copying the parents
1437              * new name and adding the child's old upRdn to new name as its Rdn
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             // Recursively change the names of the children below
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         // Get the child and the new parent to be entries and Ids
1474         BigInteger childId = getEntryId( oldChildDn.toString() );
1475         BigInteger newParentId = getEntryId( newParentDn.toString() );
1476         BigInteger oldParentId = getParentId( childId );
1477         
1478         /*
1479          * All aliases including and below oldChildDn, will be affected by
1480          * the move operation with respect to one and subtree indices since 
1481          * their relationship to ancestors above oldChildDn will be 
1482          * destroyed.  For each alias below and including oldChildDn we will
1483          * drop the index tuples mapping ancestor ids above oldChildDn to the
1484          * respective target ids of the aliases.
1485          */
1486         dropMovedAliasIndices( oldChildDn );
1487         
1488         /*
1489          * Drop the old parent child relationship and add the new one
1490          * Set the new parent id for the child replacing the old parent id
1491          */
1492         hierarchyIdx.drop( oldParentId, childId );
1493         hierarchyIdx.add( newParentId, childId );
1494 
1495         /*
1496          * Build the new user provided DN (updn) for the child using the child's
1497          * user provided RDN & the new parent's UPDN.  Basically add the child's
1498          * UpRdn String to the tail of the new parent's Updn Name.
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         // Call the modifyDn operation with the new updn
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         // Find all the aliases from movedBase down
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          * Start droping index tuples with the first ancestor right above the 
1570          * moved base.  This is the first ancestor effected by the move.
1571          */
1572         Name ancestorDn = movedBase.getSuffix( 1 );
1573         BigInteger ancestorId = getEntryId( ancestorDn.toString() );
1574         
1575         /*
1576          * We cannot just drop all tuples in the one level and subtree indices
1577          * linking baseIds to the targetId.  If more than one alias refers to
1578          * the target then droping all tuples with a value of targetId would
1579          * make all other aliases to the target inconsistent.
1580          * 
1581          * We need to walk up the path of alias ancestors right above the moved 
1582          * base until we reach the upSuffix, deleting each ( ancestorId,
1583          * targetId ) tuple in the subtree scope alias.  We only need to do 
1584          * this for the direct parent of the alias on the one level subtree if
1585          * the moved base is the alias.
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