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