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