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;
18  
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.naming.ConfigurationException;
30  import javax.naming.Name;
31  import javax.naming.NameNotFoundException;
32  import javax.naming.NamingEnumeration;
33  import javax.naming.NamingException;
34  import javax.naming.directory.Attribute;
35  import javax.naming.directory.Attributes;
36  import javax.naming.directory.BasicAttribute;
37  import javax.naming.directory.BasicAttributes;
38  import javax.naming.directory.ModificationItem;
39  import javax.naming.directory.SearchControls;
40  import javax.naming.directory.SearchResult;
41  import javax.naming.ldap.LdapContext;
42  
43  import org.apache.ldap.common.MultiException;
44  import org.apache.ldap.common.NotImplementedException;
45  import org.apache.ldap.common.schema.AttributeType;
46  import org.apache.ldap.common.schema.Normalizer;
47  import org.apache.ldap.common.exception.LdapNameNotFoundException;
48  import org.apache.ldap.common.exception.LdapInvalidAttributeIdentifierException;
49  import org.apache.ldap.common.exception.LdapNoSuchAttributeException;
50  import org.apache.ldap.common.filter.ExprNode;
51  import org.apache.ldap.common.filter.PresenceNode;
52  import org.apache.ldap.common.message.LockableAttributeImpl;
53  import org.apache.ldap.common.message.LockableAttributes;
54  import org.apache.ldap.common.message.LockableAttributesImpl;
55  import org.apache.ldap.common.name.LdapName;
56  import org.apache.ldap.common.util.DateUtils;
57  import org.apache.ldap.common.util.NamespaceTools;
58  import org.apache.ldap.common.util.SingletonEnumeration;
59  import org.apache.ldap.server.DirectoryServiceConfiguration;
60  import org.apache.ldap.server.configuration.DirectoryPartitionConfiguration;
61  import org.apache.ldap.server.configuration.MutableDirectoryPartitionConfiguration;
62  import org.apache.ldap.server.partition.impl.btree.jdbm.JdbmDirectoryPartition;
63  import org.apache.ldap.server.schema.AttributeTypeRegistry;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67                                  
68  /***
69   * A nexus for partitions dedicated for storing entries specific to a naming
70   * context.
71   * 
72   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
73   * @version $Rev: 326212 $
74   */
75  public class DefaultDirectoryPartitionNexus extends DirectoryPartitionNexus
76  {
77      private static final Logger log = LoggerFactory.getLogger( DefaultDirectoryPartitionNexus.class );
78  
79      /*** the vendorName string proudly set to: Apache Software Foundation*/
80      private static final String ASF = "Apache Software Foundation";
81  
82      /*** the vendorName DSE operational attribute */
83      private static final String VENDORNAME_ATTR = "vendorName";
84  
85      /*** the vendorVersion DSE operational attribute */
86      private static final String VENDORVERSION_ATTR = "vendorVersion";
87  
88      /*** the namingContexts DSE operational attribute */
89      private static final String NAMINGCTXS_ATTR = "namingContexts";
90  
91      /*** the closed state of this partition */
92      private boolean initialized;
93  
94      private DirectoryServiceConfiguration factoryCfg;
95  
96      /*** the system backend */
97      private DirectoryPartition system;
98  
99      /*** the backends keyed by normalized suffix strings */
100     private HashMap partitions = new HashMap();
101 
102     /*** the read only rootDSE attributes */
103     private final Attributes rootDSE;
104 
105 
106     /***
107      * Creates the root nexus singleton of the entire system.  The root DSE has
108      * several attributes that are injected into it besides those that may
109      * already exist.  As partitions are added to the system more namingContexts
110      * attributes are added to the rootDSE.
111      *
112      * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
113      */
114     public DefaultDirectoryPartitionNexus( Attributes rootDSE )
115     {
116         // setup that root DSE
117         this.rootDSE = rootDSE;
118         Attribute attr = new LockableAttributeImpl( "subschemaSubentry" );
119         attr.add( "cn=schema,ou=system" );
120         rootDSE.put( attr );
121 
122         attr = new LockableAttributeImpl( "supportedLDAPVersion" );
123         rootDSE.put( attr );
124         attr.add( "3" );
125 
126         attr = new LockableAttributeImpl( "supportedFeatures" );
127         rootDSE.put( attr );
128         attr.add( "1.3.6.1.4.1.4203.1.5.1" );
129 
130         attr = new LockableAttributeImpl( "objectClass" );
131         rootDSE.put( attr );
132         attr.add( "top" );
133         attr.add( "extensibleObject" );
134 
135         attr = new LockableAttributeImpl( NAMINGCTXS_ATTR );
136         rootDSE.put( attr );
137 
138         attr = new LockableAttributeImpl( VENDORNAME_ATTR );
139         attr.add( ASF );
140         rootDSE.put( attr );
141 
142         attr = new LockableAttributeImpl( VENDORVERSION_ATTR );
143         attr.add( "$Rev: 326212 $" );
144         rootDSE.put( attr );
145     }
146 
147 
148     public void init( DirectoryServiceConfiguration factoryCfg, DirectoryPartitionConfiguration cfg ) throws NamingException
149     {
150         // NOTE: We ignore ContextPartitionConfiguration parameter here.
151         if( initialized )
152         {
153             return;
154         }
155         
156         this.factoryCfg = factoryCfg;
157         
158         List initializedPartitionCfgs = new ArrayList();
159         initializedPartitionCfgs.add( initializeSystemPartition() );
160         
161         Iterator i = factoryCfg.getStartupConfiguration().getContextPartitionConfigurations().iterator();
162         try
163         {
164             while( i.hasNext() )
165             {
166                 DirectoryPartitionConfiguration c = ( DirectoryPartitionConfiguration ) i.next();
167                 addContextPartition( c );
168                 initializedPartitionCfgs.add( 0, c );
169             }
170             initialized = true;
171         }
172         finally
173         {
174             if( !initialized )
175             {
176                 i = initializedPartitionCfgs.iterator();
177                 while( i.hasNext() )
178                 {
179                     DirectoryPartitionConfiguration partitionCfg = 
180                         ( DirectoryPartitionConfiguration ) i.next();
181                     DirectoryPartition partition = partitionCfg.getContextPartition();
182                     i.remove();
183                     try
184                     {
185                         partition.destroy();
186                     }
187                     catch( Exception e )
188                     {
189                         log.warn(
190                                 "Failed to destroy a partition: " +
191                                 partitionCfg.getSuffix(), e );
192                     }
193                     finally
194                     {
195                         unregister( partition );
196                     }
197                 }
198             }
199         }
200     }
201 
202 
203     private DirectoryPartitionConfiguration initializeSystemPartition() throws NamingException
204     {
205         // initialize system partition first
206         MutableDirectoryPartitionConfiguration systemCfg = new MutableDirectoryPartitionConfiguration();
207         system = new JdbmDirectoryPartition(); // using default implementation.
208         systemCfg.setName( "system" );
209         systemCfg.setSuffix( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX );
210         systemCfg.setContextPartition( system );
211         
212         // Add indexed attributes for system partition
213         Set indexedSystemAttrs = new HashSet();
214         indexedSystemAttrs.add( Oid.ALIAS );
215         indexedSystemAttrs.add( Oid.EXISTANCE );
216         indexedSystemAttrs.add( Oid.HIERARCHY );
217         indexedSystemAttrs.add( Oid.NDN );
218         indexedSystemAttrs.add( Oid.ONEALIAS );
219         indexedSystemAttrs.add( Oid.SUBALIAS );
220         indexedSystemAttrs.add( Oid.UPDN );
221         systemCfg.setIndexedAttributes( indexedSystemAttrs );
222         
223         // Add context entry for system partition
224         Attributes systemEntry = new BasicAttributes( true );
225         Attribute objectClassAttr = new BasicAttribute( "objectClass" );
226         objectClassAttr.add( "top" );
227         objectClassAttr.add( "organizationalUnit" );
228         systemEntry.put( objectClassAttr );
229         systemEntry.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL ) ;
230         systemEntry.put( "createTimestamp", DateUtils.getGeneralizedTime() ) ;
231         systemEntry.put(
232                 NamespaceTools.getRdnAttribute( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX ),
233                 NamespaceTools.getRdnValue( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX ) ) ;
234         systemCfg.setContextEntry( systemEntry );
235 
236         system.init( factoryCfg, systemCfg );
237         String key = system.getSuffix( true ).toString();
238         if( partitions.containsKey( key ) )
239         {
240             throw new ConfigurationException( "Duplicate partition suffix: " + key );
241         }
242         partitions.put( key, system );
243         
244         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
245         namingContexts.add( system.getSuffix( false ).toString() );
246         
247         return systemCfg;
248     }
249 
250 
251     public boolean isInitialized()
252     {
253         return initialized;
254     }
255 
256 
257     public synchronized void destroy()
258     {
259         if ( !initialized )
260         {
261             return;
262         }
263 
264         Iterator suffixes = new HashSet( this.partitions.keySet() ).iterator();
265 
266         // make sure this loop is not fail fast so all backing stores can
267         // have an attempt at closing down and synching their cached entries
268         while ( suffixes.hasNext() )
269         {
270             String suffix = (String) suffixes.next();
271             try
272             {
273                 removeContextPartition( new LdapName( suffix ) );
274             }
275             catch ( NamingException e )
276             {
277                 log.warn( "Failed to destroy a partition: " + suffix, e );
278             }
279         }
280 
281         initialized = false;
282     }
283 
284 
285     /***
286      * @see DirectoryPartition#sync()
287      */
288     public void sync() throws NamingException
289     {
290         MultiException error = null;
291 
292         Iterator list = this.partitions.values().iterator();
293 
294         while ( list.hasNext() )
295         {
296             DirectoryPartition partition = ( DirectoryPartition ) list.next();
297 
298             try
299             {
300                 partition.sync();
301             }
302             catch ( NamingException e )
303             {
304                 log.warn( "Failed to flush partition data out.", e );
305 
306                 if ( error == null )
307                 {
308                     error = new MultiException( "Grouping many exceptions on root nexus sync()" );
309                 }
310 
311                 // @todo really need to send this info to a monitor
312                 error.addThrowable( e );
313             }
314         }
315 
316         if ( error != null )
317         {
318             String msg = "Encountered failures while performing a sync() operation on backing stores";
319 
320             NamingException total = new NamingException( msg );
321 
322             total.setRootCause( error );
323         }
324     }
325 
326 
327     // ------------------------------------------------------------------------
328     // ContextPartitionNexus Method Implementations
329     // ------------------------------------------------------------------------
330 
331 
332     public boolean compare( Name name, String oid, Object value ) throws NamingException
333     {
334         DirectoryPartition partition = getBackend( name );
335         AttributeTypeRegistry registry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
336 
337         // complain if we do not recognize the attribute being compared
338         if ( ! registry.hasAttributeType( oid ) )
339         {
340             throw new LdapInvalidAttributeIdentifierException( oid + " not found within the attributeType registry" );
341         }
342 
343         AttributeType attrType = registry.lookup( oid );
344         Attribute attr = partition.lookup( name ).get( attrType.getName() );
345 
346         // complain if the attribute being compared does not exist in the entry
347         if ( attr == null )
348         {
349             throw new LdapNoSuchAttributeException();
350         }
351 
352         // see first if simple match without normalization succeeds
353         if ( attr.contains( value ) )
354         {
355             return true;
356         }
357 
358         // now must apply normalization to all values (attr and in request) to compare
359 
360         /*
361          * Get ahold of the normalizer for the attribute and normalize the request
362          * assertion value for comparisons with normalized attribute values.  Loop
363          * through all values looking for a match.
364          */
365         Normalizer normalizer = attrType.getEquality().getNormalizer();
366         String reqVal = ( String ) normalizer.normalize( value );
367         for ( int ii = 0; ii < attr.size(); ii++ )
368         {
369             String attrVal = ( String ) normalizer.normalize( attr.get( ii ) );
370             if ( attrVal.equals( reqVal ) )
371             {
372                 return true;
373             }
374         }
375 
376         return false;
377     }
378 
379 
380     public synchronized void addContextPartition( DirectoryPartitionConfiguration config ) throws NamingException
381     {
382         DirectoryPartition partition = config.getContextPartition();
383 
384         // Turn on default indices
385         MutableDirectoryPartitionConfiguration mcfg =
386             new MutableDirectoryPartitionConfiguration();
387         mcfg.setName( config.getName() );
388         mcfg.setSuffix( config.getSuffix() );
389         mcfg.setContextEntry( config.getContextEntry() );
390         mcfg.setContextPartition( partition );
391         
392         Set indexedAttrs = config.getIndexedAttributes();
393         indexedAttrs.add( Oid.ALIAS );
394         indexedAttrs.add( Oid.EXISTANCE );
395         indexedAttrs.add( Oid.HIERARCHY );
396         indexedAttrs.add( Oid.NDN );
397         indexedAttrs.add( Oid.ONEALIAS );
398         indexedAttrs.add( Oid.SUBALIAS );
399         indexedAttrs.add( Oid.UPDN );
400         mcfg.setIndexedAttributes( indexedAttrs );
401         
402         String key = config.getSuffix();
403         if( partitions.containsKey( key ) )
404         {
405             throw new ConfigurationException( "Duplicate partition suffix: " + key );
406         }
407 
408         partition.init( factoryCfg, mcfg );
409         partitions.put( partition.getSuffix( true ).toString(), partition );
410         
411         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
412         namingContexts.add( partition.getSuffix( false ).toString() );
413     }
414 
415 
416     public synchronized void removeContextPartition( Name suffix ) throws NamingException
417     {
418         String key = suffix.toString();
419         DirectoryPartition partition = (DirectoryPartition) partitions.get( key );
420         if( partition == null )
421         {
422             throw new NameNotFoundException( "No partition with suffix: " + key );
423         }
424         
425         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
426         namingContexts.remove( partition.getSuffix( false ).toString() );
427         partitions.remove( key );
428 
429         partition.sync();
430         partition.destroy();
431     }
432     
433     public DirectoryPartition getSystemPartition()
434     {
435         return system;
436     }
437 
438     /***
439      * @see DirectoryPartitionNexus#getLdapContext()
440      */
441     public LdapContext getLdapContext() 
442     {
443         throw new NotImplementedException();
444     }
445 
446 
447     /***
448      * @see DirectoryPartitionNexus#getMatchedName(javax.naming.Name, boolean)
449      */
450     public Name getMatchedName( Name dn, boolean normalized ) throws NamingException
451     {
452         dn = ( Name ) dn.clone();
453 
454         while ( dn.size() > 0 )
455         {
456             if ( hasEntry( dn ) )
457             {
458                 return dn;
459             }
460 
461             dn = dn.getPrefix( 1 );
462         }
463 
464         return dn;
465     }
466 
467 
468     public Name getSuffix( boolean normalized )
469     {
470         return new LdapName();
471     }
472 
473 
474     /***
475      * @see org.apache.ldap.server.partition.DirectoryPartitionNexus#getSuffix(javax.naming.Name, boolean)
476      */
477     public Name getSuffix( Name dn, boolean normalized ) throws NamingException
478     {
479         DirectoryPartition backend = getBackend( dn );
480 
481         return backend.getSuffix( normalized );
482     }
483 
484 
485     /***
486      * @see org.apache.ldap.server.partition.DirectoryPartitionNexus#listSuffixes(boolean)
487      */
488     public Iterator listSuffixes( boolean normalized ) throws NamingException
489     {
490         return Collections.unmodifiableSet( partitions.keySet() ).iterator();
491     }
492 
493 
494     public Attributes getRootDSE() 
495     {
496         return rootDSE;
497     }
498 
499 
500     /***
501      * Unregisters an ContextPartition with this BackendManager.  Called for each
502      * registered Backend right befor it is to be stopped.  This prevents
503      * protocol server requests from reaching the Backend and effectively puts
504      * the ContextPartition's naming context offline.
505      *
506      * Operations against the naming context should result in an LDAP BUSY
507      * result code in the returnValue if the naming context is not online.
508      *
509      * @param partition ContextPartition component to unregister with this
510      * BackendNexus.
511      */
512     private void unregister( DirectoryPartition partition ) throws NamingException
513     {
514         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
515         namingContexts.remove( partition.getSuffix( false ).toString() );
516         partitions.remove( partition.getSuffix( true ).toString() );
517     }
518 
519 
520     // ------------------------------------------------------------------------
521     // Backend Interface Method Implementations
522     // ------------------------------------------------------------------------
523     
524     
525     /***
526      * @see DirectoryPartition#delete(javax.naming.Name)
527      */
528     public void delete( Name dn ) throws NamingException
529     {
530         DirectoryPartition backend = getBackend( dn );
531 
532         backend.delete( dn );
533     }
534 
535 
536     /***
537      * Looks up the backend corresponding to the entry first, then checks to
538      * see if the entry already exists.  If so an exception is thrown.  If not
539      * the add operation against the backend proceeds.  This check is performed
540      * here so backend implementors do not have to worry about performing these
541      * kinds of checks.
542      *
543      * @see org.apache.ldap.server.partition.DirectoryPartition#add(String, Name, Attributes)
544      */
545     public void add( String updn, Name dn, Attributes an_entry ) throws NamingException
546     {
547         DirectoryPartition backend = getBackend( dn );
548 
549         backend.add( updn, dn, an_entry );
550     }
551 
552 
553     /***
554      * @see DirectoryPartition#modify(Name, int,Attributes)
555      */
556     public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
557     {
558         DirectoryPartition backend = getBackend( dn );
559 
560         backend.modify( dn, modOp, mods );
561     }
562 
563 
564     /***
565      * @see DirectoryPartition#modify(javax.naming.Name,
566      * javax.naming.directory.ModificationItem[])
567      */
568     public void modify( Name dn, ModificationItem[] mods ) throws NamingException
569     {
570         DirectoryPartition backend = getBackend( dn );
571 
572         backend.modify( dn, mods );
573     }
574 
575     
576     /***
577      * @see DirectoryPartition#list(javax.naming.Name)
578      */
579     public NamingEnumeration list( Name base ) throws NamingException
580     {
581         DirectoryPartition backend = getBackend( base );
582 
583         return backend.list( base );
584     }
585     
586 
587     /***
588      * @see DirectoryPartition#search(Name, Map, ExprNode, SearchControls)
589      */
590     public NamingEnumeration search( Name base, Map env, ExprNode filter, SearchControls searchCtls )
591             throws NamingException
592     {
593 
594         if ( base.size() == 0 )
595         {
596             boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
597 
598             boolean isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equalsIgnoreCase( "objectclass" );
599 
600             /*
601              * if basedn is "", filter is "(objectclass=*)" and scope is object
602              * then we have a request for the rootDSE
603              */
604             if ( filter instanceof PresenceNode && isObjectScope && isSearchAll )
605             {
606                 Attributes attrs = ( Attributes ) getRootDSE().clone();
607 
608                 String[] ids = searchCtls.getReturningAttributes();
609 
610                 if ( ids != null && ids.length > 0 )
611                 {
612                     boolean doSwap = true;
613 
614                     Attributes askedFor = new LockableAttributesImpl();
615 
616                     for ( int ii = 0; ii < ids.length; ii++ )
617                     {
618                         if ( ids[ii].trim().equals( "*" ) )
619                         {
620                             doSwap = false;
621 
622                             break;
623                         }
624 
625                         if ( attrs.get( ids[ii] ) != null )
626                         {
627                             askedFor.put( attrs.get( ids[ii] ) );
628                         }
629                     }
630 
631                     if ( doSwap )
632                     {
633                         attrs = askedFor;
634                     }
635                 }
636 
637                 SearchResult result = new SearchResult( "", null, attrs, false );
638 
639                 return new SingletonEnumeration( result );
640             }
641 
642             throw new LdapNameNotFoundException();
643         }
644 
645         DirectoryPartition backend = getBackend( base );
646 
647         return backend.search( base, env, filter, searchCtls );
648     }
649 
650 
651     /***
652      * @see DirectoryPartition#lookup(javax.naming.Name)
653      */
654     public Attributes lookup( Name dn )  throws NamingException
655     {
656         if ( dn.size() == 0 )
657         {
658             LockableAttributes retval = ( LockableAttributes ) rootDSE.clone();
659 
660             retval.setLocked( true );
661 
662             return retval;
663         }
664 
665         DirectoryPartition backend = getBackend( dn );
666 
667         return backend.lookup( dn );
668     }
669 
670 
671     /***
672      * @see org.apache.ldap.server.partition.DirectoryPartition#lookup(javax.naming.Name, String[])
673      */
674     public Attributes lookup( Name dn, String[] attrIds )  throws NamingException
675     {
676         if ( dn.size() == 0 )
677         {
678             LockableAttributes retval = new LockableAttributesImpl();
679 
680             NamingEnumeration list = rootDSE.getIDs();
681 
682             while ( list.hasMore() )
683             {
684                 String id = ( String ) list.next();
685 
686                 Attribute attr = rootDSE.get( id );
687 
688                 retval.put( ( Attribute ) attr.clone() );
689             }
690 
691             retval.setLocked( true );
692 
693             return retval;
694         }
695 
696         DirectoryPartition backend = getBackend( dn );
697 
698         return backend.lookup( dn, attrIds );
699     }
700 
701 
702     /***
703      * @see DirectoryPartition#hasEntry(javax.naming.Name)
704      */
705     public boolean hasEntry( Name dn ) throws NamingException
706     {
707         if ( log.isDebugEnabled() )
708         {
709             log.debug( "Check if DN '" + dn + "' exists." );
710         }
711         
712         if ( dn.size() == 0 )
713         {
714             return true;
715         }
716 
717         DirectoryPartition backend = getBackend( dn );
718 
719         return backend.hasEntry( dn );
720     }
721 
722     
723     /***
724      * @see DirectoryPartition#isSuffix(javax.naming.Name)
725      */
726     public boolean isSuffix( Name dn )
727     {
728         return partitions.containsKey( dn.toString() );
729     }
730 
731     
732     /***
733      * @see DirectoryPartition#modifyRn(Name, String, boolean)
734      */
735     public void modifyRn( Name dn, String newRdn, boolean deleteOldRdn ) throws NamingException
736     {
737         DirectoryPartition backend = getBackend( dn );
738 
739         backend.modifyRn( dn, newRdn, deleteOldRdn );
740     }
741     
742     
743     /***
744      * @see DirectoryPartition#move(Name, Name)
745      */
746     public void move( Name oriChildName, Name newParentName ) throws NamingException
747     {
748         DirectoryPartition backend = getBackend( oriChildName );
749 
750         backend.move( oriChildName, newParentName );
751     }
752     
753     
754     /***
755      * @see DirectoryPartition#move(javax.naming.Name,
756      * javax.naming.Name, java.lang.String, boolean)
757      */
758     public void move( Name oldChildDn, Name newParentDn, String newRdn,
759         boolean deleteOldRdn ) throws NamingException
760     {
761         DirectoryPartition backend = getBackend( oldChildDn );
762 
763         backend.move( oldChildDn, newParentDn, newRdn, deleteOldRdn );
764     }
765 
766     
767     // ------------------------------------------------------------------------
768     // Private Methods
769     // ------------------------------------------------------------------------
770 
771 
772     /***
773      * Gets the backend partition associated with a normalized dn.
774      *
775      * @param dn the normalized distinguished name to resolve to a backend
776      * @return the backend partition associated with the normalized dn
777      * @throws NamingException if the name cannot be resolved to a backend
778      */
779     private DirectoryPartition getBackend( Name dn ) throws NamingException
780     {
781         Name clonedDn = ( Name ) dn.clone();
782 
783         while ( clonedDn.size() > 0 )
784         {
785             if ( partitions.containsKey( clonedDn.toString() ) )
786             {
787                 return ( DirectoryPartition ) partitions.get( clonedDn.toString() );
788             }
789             
790             clonedDn.remove( clonedDn.size() - 1 );
791         }
792         
793         throw new NameNotFoundException( dn.toString() );
794     }
795 }