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;
18  
19  
20  import org.apache.ldap.common.MultiException;
21  import org.apache.ldap.common.NotImplementedException;
22  import org.apache.ldap.common.exception.LdapNameNotFoundException;
23  import org.apache.ldap.common.filter.ExprNode;
24  import org.apache.ldap.common.filter.PresenceNode;
25  import org.apache.ldap.common.message.LockableAttributeImpl;
26  import org.apache.ldap.common.message.LockableAttributes;
27  import org.apache.ldap.common.message.LockableAttributesImpl;
28  import org.apache.ldap.common.util.SingletonEnumeration;
29  
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.*;
35  import javax.naming.ldap.LdapContext;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Map;
40  
41                                  
42  /***
43   * A nexus for partitions dedicated for storing entries specific to a naming
44   * context.
45   *
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   * @version $Rev: 159259 $
48   */
49  public class RootNexus implements PartitionNexus
50  {
51      /*** the vendorName string proudly set to: Apache Software Foundation*/
52      private static final String ASF = "Apache Software Foundation";
53  
54      /*** the vendorName DSE operational attribute */
55      private static final String VENDORNAME_ATTR = "vendorName";
56  
57      /*** the namingContexts DSE operational attribute */
58      private static final String NAMINGCTXS_ATTR = "namingContexts";
59  
60      /*** Handle on the singleton instance of this class within the entire JVM. */
61      private static RootNexus s_singleton = null;
62      
63      /*** the closed state of this partition */
64      private boolean closed = false;
65  
66      /*** the system backend */
67      private SystemPartition system;
68  
69      /*** the backends keyed by normalized suffix strings */
70      private HashMap backends = new HashMap();
71  
72      /*** the read only rootDSE attributes */
73      private final Attributes rootDSE;
74  
75  
76      /***
77       * Creates the root nexus singleton of the entire system.  The root DSE has
78       * several attributes that are injected into it besides those that may
79       * already exist.  As partitions are added to the system more namingContexts
80       * attributes are added to the rootDSE.
81       *
82       * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
83       */
84      public RootNexus( SystemPartition system, Attributes rootDSE )
85      {
86          if ( null != s_singleton )
87          {
88              throw new IllegalStateException();
89          }
90          
91          s_singleton = this;
92          this.system = system;
93  
94          // setup that root DSE
95          this.rootDSE = rootDSE;
96          Attribute attr = new LockableAttributeImpl( "subschemaSubentry" );
97          attr.add( "cn=schema,ou=system" );
98          rootDSE.put( attr );
99  
100         attr = new LockableAttributeImpl( "supportedLDAPVersion" );
101         rootDSE.put( attr );
102         attr.add( "3" );
103 
104         attr = new LockableAttributeImpl( "objectClass" );
105         rootDSE.put( attr );
106         attr.add( "top" );
107         attr.add( "extensibleObject" );
108 
109         attr = new LockableAttributeImpl( NAMINGCTXS_ATTR );
110         rootDSE.put( attr );
111 
112         attr = new LockableAttributeImpl( VENDORNAME_ATTR );
113         attr.add( ASF );
114         rootDSE.put( attr );
115 
116         // register will add to the list of namingContexts as well
117         register( this.system );
118 
119         Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
120             public void run()
121             {
122                 try
123                 {
124                     if ( ! isClosed() )
125                     {
126                         RootNexus.this.close();
127                     }
128                 }
129                 catch ( NamingException e )
130                 {
131                     e.printStackTrace();
132                     // @todo again we need to monitor this failure and report
133                     // that it occured on shutdown specifically
134                 }
135             }
136         }, "RootNexusShutdownHook" ) );
137     }
138 
139 
140     // ------------------------------------------------------------------------
141     // BackendNexus Interface Method Implementations
142     // ------------------------------------------------------------------------
143     
144 
145     /***
146      * @see PartitionNexus#getLdapContext()
147      */
148     public LdapContext getLdapContext() 
149     {
150         throw new NotImplementedException();
151     }
152 
153 
154     /***
155      * @see PartitionNexus#getMatchedDn(javax.naming.Name, boolean)
156      */
157     public Name getMatchedDn( Name dn, boolean normalized ) throws NamingException
158     {
159         dn = ( Name ) dn.clone();
160 
161         while ( dn.size() > 0 )
162         {
163             if ( hasEntry( dn ) )
164             {
165                 return dn;
166             }
167 
168             dn = dn.getSuffix( 1 );
169         }
170 
171         return dn;
172     }
173 
174 
175     /***
176      * @see org.apache.ldap.server.PartitionNexus#getSuffix(javax.naming.Name, boolean)
177      */
178     public Name getSuffix( Name dn, boolean normalized ) throws NamingException
179     {
180         ContextPartition backend = getBackend( dn );
181 
182         return backend.getSuffix( normalized );
183     }
184 
185 
186     /***
187      * @see org.apache.ldap.server.PartitionNexus#listSuffixes(boolean)
188      */
189     public Iterator listSuffixes( boolean normalized ) throws NamingException
190     {
191         return Collections.unmodifiableSet( backends.keySet() ).iterator();
192     }
193 
194 
195     /***
196      * Get's the RootDSE entry for the DSA.
197      *
198      * @return the attributes of the RootDSE
199      */
200     public Attributes getRootDSE() 
201     {
202         return rootDSE;
203     }
204 
205 
206     /***
207      * @see org.apache.ldap.server.PartitionNexus#register(
208      * ContextPartition)
209      */
210     public void register( ContextPartition backend )
211     {
212         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
213 
214         namingContexts.add( backend.getSuffix( false ).toString() );
215 
216         backends.put( backend.getSuffix( true ).toString(), backend );
217     }
218 
219 
220     /***
221      * @see PartitionNexus#unregister(
222      * ContextPartition)
223      */
224     public void unregister( ContextPartition backend )
225     {
226         Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
227 
228         namingContexts.remove( backend.getSuffix( false ).toString() );
229 
230         backends.remove( backend.getSuffix( true ).toString() );
231     }
232 
233 
234     // ------------------------------------------------------------------------
235     // Backend Interface Method Implementations
236     // ------------------------------------------------------------------------
237     
238     
239     /***
240      * @see BackingStore#delete(javax.naming.Name)
241      */
242     public void delete( Name dn ) throws NamingException
243     {
244         ContextPartition backend = getBackend( dn );
245 
246         backend.delete( dn );
247     }
248 
249 
250     /***
251      * Looks up the backend corresponding to the entry first, then checks to
252      * see if the entry already exists.  If so an exception is thrown.  If not
253      * the add operation against the backend proceeds.  This check is performed
254      * here so backend implementors do not have to worry about performing these
255      * kinds of checks.
256      *
257      * @see org.apache.ldap.server.BackingStore#add(String, Name, Attributes)
258      */
259     public void add( String updn, Name dn, Attributes an_entry ) throws NamingException
260     {
261         ContextPartition backend = getBackend( dn );
262 
263         backend.add( updn, dn, an_entry );
264     }
265 
266 
267     /***
268      * @see BackingStore#modify(Name, int,Attributes)
269      */
270     public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
271     {
272         ContextPartition backend = getBackend( dn );
273 
274         backend.modify( dn, modOp, mods );
275     }
276 
277 
278     /***
279      * @see BackingStore#modify(javax.naming.Name,
280      * javax.naming.directory.ModificationItem[])
281      */
282     public void modify( Name dn, ModificationItem[] mods ) throws NamingException
283     {
284         ContextPartition backend = getBackend( dn );
285 
286         backend.modify( dn, mods );
287     }
288 
289     
290     /***
291      * @see BackingStore#list(javax.naming.Name)
292      */
293     public NamingEnumeration list( Name base ) throws NamingException
294     {
295         ContextPartition backend = getBackend( base );
296 
297         return backend.list( base );
298     }
299     
300 
301     /***
302      * @see BackingStore#search(Name, Map, ExprNode, SearchControls)
303      */
304     public NamingEnumeration search( Name base, Map env, ExprNode filter, SearchControls searchCtls )
305             throws NamingException
306     {
307 
308         if ( base.size() == 0 )
309         {
310             boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
311 
312             boolean isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equalsIgnoreCase( "objectclass" );
313 
314             /*
315              * if basedn is "", filter is "(objectclass=*)" and scope is object
316              * then we have a request for the rootDSE
317              */
318             if ( filter instanceof PresenceNode && isObjectScope && isSearchAll )
319             {
320                 Attributes attrs = ( Attributes ) getRootDSE().clone();
321 
322                 String[] ids = searchCtls.getReturningAttributes();
323 
324                 if ( ids != null && ids.length > 0 )
325                 {
326                     boolean doSwap = true;
327 
328                     Attributes askedFor = new LockableAttributesImpl();
329 
330                     for ( int ii = 0; ii < ids.length; ii++ )
331                     {
332                         if ( ids[ii].trim().equals( "*" ) )
333                         {
334                             doSwap = false;
335 
336                             break;
337                         }
338 
339                         if ( attrs.get( ids[ii] ) != null )
340                         {
341                             askedFor.put( attrs.get( ids[ii] ) );
342                         }
343                     }
344 
345                     if ( doSwap )
346                     {
347                         attrs = askedFor;
348                     }
349                 }
350 
351                 SearchResult result = new SearchResult( "", null, attrs, false );
352 
353                 return new SingletonEnumeration( result );
354             }
355 
356             throw new LdapNameNotFoundException();
357         }
358 
359         ContextPartition backend = getBackend( base );
360 
361         return backend.search( base, env, filter, searchCtls );
362     }
363 
364 
365     /***
366      * @see BackingStore#lookup(javax.naming.Name)
367      */
368     public Attributes lookup( Name dn )  throws NamingException
369     {
370         if ( dn.size() == 0 )
371         {
372             LockableAttributes retval = ( LockableAttributes ) rootDSE.clone();
373 
374             retval.setLocked( true );
375 
376             return retval;
377         }
378 
379         ContextPartition backend = getBackend( dn );
380 
381         return backend.lookup( dn );
382     }
383 
384 
385     /***
386      * @see org.apache.ldap.server.BackingStore#lookup(javax.naming.Name, String[])
387      */
388     public Attributes lookup( Name dn, String[] attrIds )  throws NamingException
389     {
390         if ( dn.size() == 0 )
391         {
392             LockableAttributes retval = new LockableAttributesImpl();
393 
394             NamingEnumeration list = rootDSE.getIDs();
395 
396             while ( list.hasMore() )
397             {
398                 String id = ( String ) list.next();
399 
400                 Attribute attr = rootDSE.get( id );
401 
402                 retval.put( ( Attribute ) attr.clone() );
403             }
404 
405             retval.setLocked( true );
406 
407             return retval;
408         }
409 
410         ContextPartition backend = getBackend( dn );
411 
412         return backend.lookup( dn, attrIds );
413     }
414 
415 
416     /***
417      * @see BackingStore#hasEntry(javax.naming.Name)
418      */
419     public boolean hasEntry( Name dn ) throws NamingException
420     {
421         if ( dn.size() == 0 )
422         {
423             return true;
424         }
425 
426         ContextPartition backend = getBackend( dn );
427 
428         return backend.hasEntry( dn );
429     }
430 
431     
432     /***
433      * @see BackingStore#isSuffix(javax.naming.Name)
434      */
435     public boolean isSuffix( Name dn ) throws NamingException
436     {
437         return backends.containsKey( dn.toString() );
438     }
439 
440     
441     /***
442      * @see BackingStore#modifyRn(Name, String, boolean)
443      */
444     public void modifyRn( Name dn, String newRdn, boolean deleteOldRdn ) throws NamingException
445     {
446         ContextPartition backend = getBackend( dn );
447 
448         backend.modifyRn( dn, newRdn, deleteOldRdn );
449     }
450     
451     
452     /***
453      * @see BackingStore#move(Name, Name)
454      */
455     public void move( Name oriChildName, Name newParentName ) throws NamingException
456     {
457         ContextPartition backend = getBackend( oriChildName );
458 
459         backend.move( oriChildName, newParentName );
460     }
461     
462     
463     /***
464      * @see BackingStore#move(javax.naming.Name,
465      * javax.naming.Name, java.lang.String, boolean)
466      */
467     public void move( Name oldChildDn, Name newParentDn, String newRdn,
468         boolean deleteOldRdn ) throws NamingException
469     {
470         ContextPartition backend = getBackend( oldChildDn );
471 
472         backend.move( oldChildDn, newParentDn, newRdn, deleteOldRdn );
473     }
474 
475 
476     /***
477      * @see BackingStore#sync()
478      */
479     public void sync() throws NamingException
480     {
481         MultiException error = null;
482 
483         Iterator list = this.backends.values().iterator();
484 
485         while ( list.hasNext() )
486         {
487             BackingStore store = ( BackingStore ) list.next();
488 
489             try
490             {
491                 store.sync();
492             }
493             catch ( NamingException e )
494             {
495                 e.printStackTrace();
496 
497                 if ( error == null )
498                 {
499                     error = new MultiException( "Grouping many exceptions on root nexus sync()" );
500                 }
501 
502                 // @todo really need to send this info to a monitor
503                 error.addThrowable( e );
504             }
505         }
506 
507         if ( error != null )
508         {
509             String msg = "Encountered failures while performing a sync() operation on backing stores";
510 
511             NamingException total = new NamingException( msg );
512 
513             total.setRootCause( error );
514         }
515     }
516 
517 
518     /***
519      * @see ContextPartition#isClosed()
520      */
521     public boolean isClosed()
522     {
523         return closed;
524     }
525 
526 
527     /***
528      * @see org.apache.ldap.server.BackingStore#close()
529      */
530     public synchronized void close() throws NamingException
531     {
532         if ( closed )
533         {
534             return;
535         }
536 
537         MultiException error = null;
538 
539         Iterator list = this.backends.values().iterator();
540 
541         // make sure this loop is not fail fast so all backing stores can
542         // have an attempt at closing down and synching their cached entries
543         while ( list.hasNext() )
544         {
545             BackingStore store = ( BackingStore ) list.next();
546 
547             try
548             {
549                 store.sync();
550 
551                 store.close();
552             }
553             catch ( NamingException e )
554             {
555                 e.printStackTrace();
556 
557                 if ( error == null )
558                 {
559                     error = new MultiException( "Grouping many exceptions on root nexus close()" );
560                 }
561 
562                 // @todo really need to send this info to a monitor
563                 error.addThrowable( e );
564             }
565         }
566 
567         s_singleton = null;
568 
569         closed = true;
570 
571         if ( error != null )
572         {
573             String msg = "Encountered failures while performing a close() operation on backing stores";
574 
575             NamingException total = new NamingException( msg );
576 
577             total.setRootCause( error );
578 
579             throw total;
580         }
581     }
582 
583 
584     // ------------------------------------------------------------------------
585     // Private Methods
586     // ------------------------------------------------------------------------
587 
588 
589     /***
590      * Gets the backend partition associated with a normalized dn.
591      *
592      * @param dn the normalized distinguished name to resolve to a backend
593      * @return the backend partition associated with the normalized dn
594      * @throws NamingException if the name cannot be resolved to a backend
595      */
596     private ContextPartition getBackend( Name dn ) throws NamingException
597     {
598         Name clonedDn = ( Name ) dn.clone();
599 
600         while ( clonedDn.size() > 0 )
601         {
602             if ( backends.containsKey( clonedDn.toString() ) )
603             {
604                 return ( ContextPartition ) backends.get( clonedDn.toString() );
605             }
606             
607             clonedDn.remove( clonedDn.size() - 1 );
608         }
609         
610         throw new NameNotFoundException();
611     }
612 }