001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.core.partition;
021    
022    
023    import java.io.IOException;
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Properties;
032    import java.util.Set;
033    import java.util.UUID;
034    
035    import javax.naming.ConfigurationException;
036    import javax.naming.NameNotFoundException;
037    import javax.naming.directory.SearchControls;
038    
039    import org.apache.directory.server.constants.ServerDNConstants;
040    import org.apache.directory.server.core.CoreSession;
041    import org.apache.directory.server.core.DefaultCoreSession;
042    import org.apache.directory.server.core.DirectoryService;
043    import org.apache.directory.server.core.LdapPrincipal;
044    import org.apache.directory.server.core.entry.ClonedServerEntry;
045    import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
046    import org.apache.directory.server.core.filtering.CursorList;
047    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
048    import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
049    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
050    import org.apache.directory.server.core.interceptor.context.BindOperationContext;
051    import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
052    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
053    import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
054    import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
055    import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
056    import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
057    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
058    import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
059    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
060    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
061    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
062    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
063    import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
064    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
065    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
066    import org.apache.directory.server.core.interceptor.context.UnbindOperationContext;
067    import org.apache.directory.server.i18n.I18n;
068    import org.apache.directory.shared.ldap.MultiException;
069    import org.apache.directory.shared.ldap.NotImplementedException;
070    import org.apache.directory.shared.ldap.codec.controls.CascadeControl;
071    import org.apache.directory.shared.ldap.codec.controls.ManageDsaITControl;
072    import org.apache.directory.shared.ldap.codec.controls.replication.syncDoneValue.SyncDoneValueControl;
073    import org.apache.directory.shared.ldap.codec.controls.replication.syncInfoValue.SyncInfoValueControl;
074    import org.apache.directory.shared.ldap.codec.controls.replication.syncRequestValue.SyncRequestValueControl;
075    import org.apache.directory.shared.ldap.codec.controls.replication.syncStateValue.SyncStateValueControl;
076    import org.apache.directory.shared.ldap.codec.search.controls.entryChange.EntryChangeControl;
077    import org.apache.directory.shared.ldap.codec.search.controls.pagedSearch.PagedResultsControl;
078    import org.apache.directory.shared.ldap.codec.search.controls.persistentSearch.PersistentSearchControl;
079    import org.apache.directory.shared.ldap.codec.search.controls.subentries.SubentriesControl;
080    import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
081    import org.apache.directory.shared.ldap.constants.SchemaConstants;
082    import org.apache.directory.shared.ldap.cursor.EmptyCursor;
083    import org.apache.directory.shared.ldap.cursor.SingletonCursor;
084    import org.apache.directory.shared.ldap.entry.DefaultServerAttribute;
085    import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
086    import org.apache.directory.shared.ldap.entry.EntryAttribute;
087    import org.apache.directory.shared.ldap.entry.ServerEntry;
088    import org.apache.directory.shared.ldap.entry.Value;
089    import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException;
090    import org.apache.directory.shared.ldap.exception.LdapNoSuchObjectException;
091    import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
092    import org.apache.directory.shared.ldap.filter.ExprNode;
093    import org.apache.directory.shared.ldap.filter.PresenceNode;
094    import org.apache.directory.shared.ldap.filter.SearchScope;
095    import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
096    import org.apache.directory.shared.ldap.name.DN;
097    import org.apache.directory.shared.ldap.schema.AttributeType;
098    import org.apache.directory.shared.ldap.schema.Normalizer;
099    import org.apache.directory.shared.ldap.schema.SchemaManager;
100    import org.apache.directory.shared.ldap.schema.UsageEnum;
101    import org.apache.directory.shared.ldap.util.DateUtils;
102    import org.apache.directory.shared.ldap.util.NamespaceTools;
103    import org.apache.directory.shared.ldap.util.StringTools;
104    import org.apache.directory.shared.ldap.util.tree.DnBranchNode;
105    import org.slf4j.Logger;
106    import org.slf4j.LoggerFactory;
107    
108    
109    /**
110     * A root {@link Partition} that contains all other partitions, and
111     * routes all operations to the child partition that matches to its base suffixes.
112     * It also provides some extended operations such as accessing rootDSE and
113     * listing base suffixes.
114     *
115     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
116     * @version $Rev: 927404 $, $Date: 2010-03-25 14:55:18 +0100 (Thu, 25 Mar 2010) $
117     */
118    public class DefaultPartitionNexus  extends AbstractPartition implements PartitionNexus
119    {
120        /** A logger for this class */
121        private static final Logger LOG = LoggerFactory.getLogger( DefaultPartitionNexus.class );
122    
123        /** Speedup for logs */
124        private static final boolean IS_DEBUG = LOG.isDebugEnabled();
125    
126        /** the vendorName string proudly set to: Apache Software Foundation*/
127        private static final String ASF = "Apache Software Foundation";
128    
129        /** the read only rootDSE attributes */
130        private final ServerEntry rootDSE;
131    
132        /** The DirectoryService instance */
133        private DirectoryService directoryService;
134        
135        /** The global schemaManager */
136        private SchemaManager schemaManager;
137        
138        /** the partitions keyed by normalized suffix strings */
139        private Map<String, Partition> partitions = new HashMap<String, Partition>();
140        
141        /** A structure to hold all the partitions */
142        private DnBranchNode<Partition> partitionLookupTree = new DnBranchNode<Partition>();
143        
144        /** the system partition */
145        private Partition system;
146    
147        /** the closed state of this partition */
148        private boolean initialized;
149        
150       
151        /**
152         * Creates the root nexus singleton of the entire system.  The root DSE has
153         * several attributes that are injected into it besides those that may
154         * already exist.  As partitions are added to the system more namingContexts
155         * attributes are added to the rootDSE.
156         *
157         * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
158         * @param rootDSE the root entry for the DSA
159         * @throws javax.naming.Exception on failure to initialize
160         */
161        public DefaultPartitionNexus( ServerEntry rootDSE ) throws Exception
162        {
163            // setup that root DSE
164            this.rootDSE = rootDSE;
165            
166            // Add the basic informations
167            rootDSE.put( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, ServerDNConstants.CN_SCHEMA_DN );
168            rootDSE.put( SchemaConstants.SUPPORTED_LDAP_VERSION_AT, "3" );
169            rootDSE.put( SchemaConstants.SUPPORTED_FEATURES_AT, SchemaConstants.FEATURE_ALL_OPERATIONAL_ATTRIBUTES );
170            rootDSE.put( SchemaConstants.SUPPORTED_EXTENSION_AT, NoticeOfDisconnect.EXTENSION_OID );
171    
172            // Add the supported controls
173            rootDSE.put( 
174                SchemaConstants.SUPPORTED_CONTROL_AT, 
175                PersistentSearchControl.CONTROL_OID,
176                EntryChangeControl.CONTROL_OID,
177                SubentriesControl.CONTROL_OID,
178                ManageDsaITControl.CONTROL_OID,
179                CascadeControl.CONTROL_OID,
180                PagedResultsControl.CONTROL_OID,
181                // Replication controls
182                SyncDoneValueControl.CONTROL_OID,
183                SyncInfoValueControl.CONTROL_OID,
184                SyncRequestValueControl.CONTROL_OID,
185                SyncStateValueControl.CONTROL_OID 
186            );
187    
188            // Add the objectClasses
189            rootDSE.put( SchemaConstants.OBJECT_CLASS_AT,
190                SchemaConstants.TOP_OC,
191                SchemaConstants.EXTENSIBLE_OBJECT_OC );
192    
193            // Add the 'vendor' name and version infos
194            rootDSE.put( SchemaConstants.VENDOR_NAME_AT, ASF );
195    
196            Properties props = new Properties();
197            
198            try
199            {
200                props.load( getClass().getResourceAsStream( "version.properties" ) );
201            }
202            catch ( IOException e )
203            {
204                LOG.error( I18n.err( I18n.ERR_33 ) );
205            }
206    
207            rootDSE.put( SchemaConstants.VENDOR_VERSION_AT, props.getProperty( "apacheds.version", "UNKNOWN" ) );
208        }
209    
210        
211        /* (non-Javadoc)
212         * @see org.apache.directory.server.core.partition.PartitionNexus#initialize()
213         */
214        protected void doInit( ) throws Exception
215        {
216            // NOTE: We ignore ContextPartitionConfiguration parameter here.
217            if ( initialized )
218            {
219                return;
220            }
221        
222            //this.directoryService = directoryService;
223            schemaManager = directoryService.getSchemaManager();
224            
225            // Initialize and normalize the localy used DNs
226            DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN );
227            adminDn.normalize( schemaManager.getNormalizerMapping() );
228                
229            initializeSystemPartition( directoryService );
230            
231            List<Partition> initializedPartitions = new ArrayList<Partition>();
232            initializedPartitions.add( 0, this.system );
233    
234        
235            try
236            {
237                for ( Partition partition : directoryService.getPartitions() )
238                {
239                    partition.setSchemaManager( schemaManager );
240                    CoreSession adminSession = new DefaultCoreSession( 
241                        new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
242        
243                    AddContextPartitionOperationContext opCtx = 
244                        new AddContextPartitionOperationContext( adminSession, partition );
245                    addContextPartition( opCtx );
246                    initializedPartitions.add( opCtx.getPartition() );
247                }
248                
249                initialized = true;
250            }
251            finally
252            {
253                if ( !initialized )
254                {
255                    Iterator<Partition> i = initializedPartitions.iterator();
256                    while ( i.hasNext() )
257                    {
258                        Partition partition = i.next();
259                        i.remove();
260                        try
261                        {
262                            partition.destroy();
263                        }
264                        catch ( Exception e )
265                        {
266                            LOG.warn( "Failed to destroy a partition: " + partition.getSuffixDn(), e );
267                        }
268                        finally
269                        {
270                            unregister( partition );
271                        }
272                    }
273                }
274            }
275        }
276    
277    
278        private Partition initializeSystemPartition( DirectoryService directoryService ) throws Exception
279        {
280            // initialize system partition first
281            Partition override = directoryService.getSystemPartition();
282            
283            if ( override != null )
284            {
285                
286                // ---------------------------------------------------------------
287                // check a few things to make sure users configured it properly
288                // ---------------------------------------------------------------
289    
290                if ( ! override.getId().equals( "system" ) )
291                {
292                    throw new ConfigurationException( I18n.err( I18n.ERR_262, override.getId() ) );
293                }
294                
295    
296                system = override;
297            }
298            else
299            {
300            }
301    
302            system.initialize( );
303            
304            
305            // Add root context entry for system partition
306            DN systemSuffixDn = new DN( ServerDNConstants.SYSTEM_DN );
307            systemSuffixDn.normalize( schemaManager.getNormalizerMapping() );
308            ServerEntry systemEntry = new DefaultServerEntry( schemaManager, systemSuffixDn );
309    
310            // Add the ObjectClasses
311            systemEntry.put( SchemaConstants.OBJECT_CLASS_AT,
312                SchemaConstants.TOP_OC,
313                SchemaConstants.ORGANIZATIONAL_UNIT_OC,
314                SchemaConstants.EXTENSIBLE_OBJECT_OC
315                );
316            
317            // Add some operational attributes
318            systemEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN );
319            systemEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
320            systemEntry.add( SchemaConstants.ENTRY_CSN_AT, directoryService.getCSN().toString() );
321            systemEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
322            systemEntry.put( NamespaceTools.getRdnAttribute( ServerDNConstants.SYSTEM_DN ),
323                NamespaceTools.getRdnValue( ServerDNConstants.SYSTEM_DN ) );
324            DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
325            adminDn.normalize( schemaManager.getNormalizerMapping() );
326            CoreSession adminSession = new DefaultCoreSession( 
327                new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
328            AddOperationContext addOperationContext = new AddOperationContext( adminSession, systemEntry );
329            
330            if ( !system.hasEntry( new EntryOperationContext( adminSession, systemEntry.getDn() ) ) )
331            {
332                system.add( addOperationContext );
333            }
334            
335            String key = system.getSuffixDn().getName();
336            
337            if ( partitions.containsKey( key ) )
338            {
339                throw new ConfigurationException( I18n.err( I18n.ERR_263, key ) );
340            }
341            
342            synchronized ( partitionLookupTree )
343            {
344                partitions.put( key, system );
345                partitionLookupTree.add( system.getSuffixDn(), system );
346                EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
347                
348                if ( namingContexts == null )
349                {
350                    namingContexts = new DefaultServerAttribute( 
351                        schemaManager.lookupAttributeTypeRegistry( SchemaConstants.NAMING_CONTEXTS_AT ), 
352                        system.getSuffixDn().getName() );
353                    rootDSE.put( namingContexts );
354                }
355                else
356                {
357                    namingContexts.add( system.getSuffixDn().getName() );
358                }
359            }
360    
361            return system;
362        }
363        
364        
365        /* (non-Javadoc)
366         * @see org.apache.directory.server.core.partition.PartitionNexus#destroy()
367         */
368        protected synchronized void doDestroy()
369        {
370            if ( !initialized )
371            {
372                return;
373            }
374    
375            // make sure this loop is not fail fast so all backing stores can
376            // have an attempt at closing down and synching their cached entries
377            for ( String suffix : new HashSet<String>( this.partitions.keySet() ) )
378            {
379                try
380                {
381                    DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
382                    adminDn.normalize( schemaManager.getNormalizerMapping() );
383                    CoreSession adminSession = new DefaultCoreSession( 
384                        new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
385                    removeContextPartition( new RemoveContextPartitionOperationContext( 
386                        adminSession, new DN( suffix ) ) );
387                }
388                catch ( Exception e )
389                {
390                    LOG.warn( "Failed to destroy a partition: " + suffix, e );
391                }
392            }
393    
394            initialized = false;
395        }
396    
397    
398        /* (non-Javadoc)
399         * @see org.apache.directory.server.core.partition.PartitionNexus#getId()
400         */
401        public String getId()
402        {
403            return "NEXUS";
404        }
405    
406    
407        /* (non-Javadoc)
408         * @see org.apache.directory.server.core.partition.PartitionNexus#setId(java.lang.String)
409         */
410        public void setId( String id )
411        {
412            throw new UnsupportedOperationException( I18n.err( I18n.ERR_264 ) );
413        }
414    
415    
416        /**
417         * {@inheritDoc}
418         */
419        public SchemaManager getSchemaManager()
420        {
421            return schemaManager;
422        }
423        
424    
425        /**
426         * {@inheritDoc}
427         */
428        public void setSchemaManager( SchemaManager schemaManager )
429        {
430            this.schemaManager = schemaManager;
431        }
432        
433        
434        /* (non-Javadoc)
435         * @see org.apache.directory.server.core.partition.PartitionNexus#getSuffixDn()
436         */
437        public DN getSuffixDn()
438        {
439            return DN.EMPTY_DN;
440        }
441    
442        
443        /* (non-Javadoc)
444         * @see org.apache.directory.server.core.partition.PartitionNexus#getSuffix()
445         */
446        public String getSuffix()
447        {
448            return StringTools.EMPTY;
449        }
450        
451        
452        /* (non-Javadoc)
453         * @see org.apache.directory.server.core.partition.PartitionNexus#setSuffix(java.lang.String)
454         */
455        public void setSuffix( String suffix )
456        {
457            throw new UnsupportedOperationException();
458        }
459    
460    
461        /* (non-Javadoc)
462         * @see org.apache.directory.server.core.partition.PartitionNexus#isInitialized()
463         */
464        public boolean isInitialized()
465        {
466            return initialized;
467        }
468    
469    
470        /* (non-Javadoc)
471         * @see org.apache.directory.server.core.partition.PartitionNexus#sync()
472         */
473        public void sync() throws Exception
474        {
475            MultiException error = null;
476    
477            for ( Partition partition : this.partitions.values() )
478            {
479                try
480                {
481                    partition.sync();
482                }
483                catch ( Exception e )
484                {
485                    LOG.warn( "Failed to flush partition data out.", e );
486                    if ( error == null )
487                    {
488                        //noinspection ThrowableInstanceNeverThrown
489                        error = new MultiException( I18n.err( I18n.ERR_265 ) );
490                    }
491    
492                    // @todo really need to send this info to a monitor
493                    error.addThrowable( e );
494                }
495            }
496    
497            if ( error != null )
498            {
499                throw error;
500            }
501        }
502        
503        // ------------------------------------------------------------------------
504        // DirectoryPartition Interface Method Implementations
505        // ------------------------------------------------------------------------
506        /* (non-Javadoc)
507         * @see org.apache.directory.server.core.partition.PartitionNexus#add(org.apache.directory.server.core.interceptor.context.AddOperationContext)
508         */
509        public void add( AddOperationContext addContext ) throws Exception
510        {
511            Partition backend = getPartition( addContext.getDn() );
512            backend.add( addContext );
513        }
514        
515        
516        /* (non-Javadoc)
517         * @see org.apache.directory.server.core.partition.PartitionNexus#bind(org.apache.directory.server.core.interceptor.context.BindOperationContext)
518         */
519        public void bind( BindOperationContext bindContext ) throws Exception
520        {
521            Partition partition = getPartition( bindContext.getDn() );
522            partition.bind( bindContext );
523        }
524    
525        
526        /* (non-Javadoc)
527         * @see org.apache.directory.server.core.partition.PartitionNexus#compare(org.apache.directory.server.core.interceptor.context.CompareOperationContext)
528         */
529        public boolean compare( CompareOperationContext compareContext ) throws Exception
530        {
531            Partition partition = getPartition( compareContext.getDn() );
532            //AttributeTypeRegistry registry = schemaManager.getAttributeTypeRegistry();
533            
534            // complain if we do not recognize the attribute being compared
535            if ( !schemaManager.getAttributeTypeRegistry().contains( compareContext.getOid() ) )
536            {
537                throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_266, compareContext.getOid() ) );
538            }
539    
540            AttributeType attrType = schemaManager.lookupAttributeTypeRegistry( compareContext.getOid() );
541            
542            EntryAttribute attr = partition.lookup( compareContext.newLookupContext( 
543                compareContext.getDn() ) ).get( attrType.getName() );
544    
545            // complain if the attribute being compared does not exist in the entry
546            if ( attr == null )
547            {
548                throw new LdapNoSuchAttributeException();
549            }
550    
551            // see first if simple match without normalization succeeds
552            if ( attr.contains( (Value<?>)compareContext.getValue()  ) )
553            {
554                return true;
555            }
556    
557            // now must apply normalization to all values (attr and in request) to compare
558    
559            /*
560             * Get ahold of the normalizer for the attribute and normalize the request
561             * assertion value for comparisons with normalized attribute values.  Loop
562             * through all values looking for a match.
563             */
564            Normalizer normalizer = attrType.getEquality().getNormalizer();
565            Value<?> reqVal = normalizer.normalize( compareContext.getValue() );
566    
567            for ( Value<?> value:attr )
568            {
569                Value<?> attrValObj = normalizer.normalize( value );
570                
571                if ( attrValObj.equals( reqVal ) )
572                {
573                    return true;
574                }
575            }
576    
577            return false;
578        }
579    
580    
581        /* (non-Javadoc)
582         * @see org.apache.directory.server.core.partition.PartitionNexus#delete(org.apache.directory.server.core.interceptor.context.DeleteOperationContext)
583         */
584        public void delete( DeleteOperationContext deleteContext ) throws Exception
585        {
586            Partition backend = getPartition( deleteContext.getDn() );
587            backend.delete( deleteContext );
588        }
589    
590    
591        /* (non-Javadoc)
592         * @see org.apache.directory.server.core.partition.PartitionNexus#hasEntry(org.apache.directory.server.core.interceptor.context.EntryOperationContext)
593         */
594        public boolean hasEntry( EntryOperationContext opContext ) throws Exception
595        {
596            DN dn = opContext.getDn();
597            
598            if ( IS_DEBUG )
599            {
600                LOG.debug( "Check if DN '" + dn + "' exists." );
601            }
602    
603            if ( dn.size() == 0 )
604            {
605                return true;
606            }
607    
608            Partition backend = getPartition( dn );
609            return backend.hasEntry( opContext );
610        }
611    
612    
613        /* (non-Javadoc)
614         * @see org.apache.directory.server.core.partition.PartitionNexus#list(org.apache.directory.server.core.interceptor.context.ListOperationContext)
615         */
616        public EntryFilteringCursor list( ListOperationContext opContext ) throws Exception
617        {
618            Partition backend = getPartition( opContext.getDn() );
619            return backend.list( opContext );
620        }
621    
622    
623        /* (non-Javadoc)
624         * @see org.apache.directory.server.core.partition.PartitionNexus#lookup(org.apache.directory.server.core.interceptor.context.LookupOperationContext)
625         */
626        public ClonedServerEntry lookup( LookupOperationContext opContext ) throws Exception
627        {
628            DN dn = opContext.getDn();
629            
630            if ( dn.size() == 0 )
631            {
632                ClonedServerEntry retval = new ClonedServerEntry( rootDSE );
633                Set<AttributeType> attributeTypes = rootDSE.getAttributeTypes();
634         
635                if ( opContext.getAttrsId() != null && ! opContext.getAttrsId().isEmpty() )
636                {
637                    for ( AttributeType attributeType:attributeTypes )
638                    {
639                        String oid = attributeType.getOid();
640                        
641                        if ( ! opContext.getAttrsId().contains( oid ) )
642                        {
643                            retval.removeAttributes( attributeType );
644                        }
645                    }
646                    return retval;
647                }
648                else
649                {
650                    return new ClonedServerEntry( rootDSE );
651                }
652            }
653    
654            Partition backend = getPartition( dn );
655            return backend.lookup( opContext );
656        }
657    
658    
659        /* (non-Javadoc)
660         * @see org.apache.directory.server.core.partition.PartitionNexus#lookup(java.lang.Long)
661         */
662        public ClonedServerEntry lookup( Long id ) throws Exception
663        {
664            // TODO not implemented until we can use id to figure out the partition using
665            // the partition ID component of the 64 bit Long identifier
666            throw new NotImplementedException();
667        }
668    
669        
670        /* (non-Javadoc)
671         * @see org.apache.directory.server.core.partition.PartitionNexus#modify(org.apache.directory.server.core.interceptor.context.ModifyOperationContext)
672         */
673        public void modify( ModifyOperationContext modifyContext ) throws Exception
674        {
675            // Special case : if we don't have any modification to apply, just return
676            if ( modifyContext.getModItems().size() == 0 )
677            {
678                return;
679            }
680            
681            Partition backend = getPartition( modifyContext.getDn() );
682            backend.modify( modifyContext );
683        }
684    
685    
686        /* (non-Javadoc)
687         * @see org.apache.directory.server.core.partition.PartitionNexus#move(org.apache.directory.server.core.interceptor.context.MoveOperationContext)
688         */
689        public void move( MoveOperationContext opContext ) throws Exception
690        {
691            Partition backend = getPartition( opContext.getDn() );
692            backend.move( opContext );
693        }
694    
695    
696        /* (non-Javadoc)
697         * @see org.apache.directory.server.core.partition.PartitionNexus#moveAndRename(org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext)
698         */
699        public void moveAndRename( MoveAndRenameOperationContext opContext ) throws Exception
700        {
701            Partition backend = getPartition( opContext.getDn() );
702            backend.moveAndRename( opContext );
703        }
704    
705    
706        /* (non-Javadoc)
707         * @see org.apache.directory.server.core.partition.PartitionNexus#rename(org.apache.directory.server.core.interceptor.context.RenameOperationContext)
708         */
709        public void rename( RenameOperationContext opContext ) throws Exception
710        {
711            Partition backend = getPartition( opContext.getDn() );
712            backend.rename( opContext );
713        }
714    
715        
716        private EntryFilteringCursor searchRootDSE( SearchOperationContext searchOperationContext ) throws Exception
717        {
718            SearchControls searchControls = searchOperationContext.getSearchControls();
719            
720            String[] ids = searchControls.getReturningAttributes();
721    
722            // -----------------------------------------------------------
723            // If nothing is asked for then we just return the entry asis.
724            // We let other mechanisms filter out operational attributes.
725            // -----------------------------------------------------------
726            if ( ( ids == null ) || ( ids.length == 0 ) )
727            {
728                ServerEntry rootDSE = (ServerEntry)getRootDSE( null ).clone();
729                return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( rootDSE ), searchOperationContext );
730            }
731            
732            // -----------------------------------------------------------
733            // Collect all the real attributes besides 1.1, +, and * and
734            // note if we've seen these special attributes as well.
735            // -----------------------------------------------------------
736    
737            Set<String> realIds = new HashSet<String>();
738            boolean allUserAttributes = searchOperationContext.isAllUserAttributes();
739            boolean allOperationalAttributes = searchOperationContext.isAllOperationalAttributes();
740            boolean noAttribute = searchOperationContext.isNoAttributes();
741    
742            for ( String id:ids )
743            {
744                String idTrimmed = id.trim();
745                
746                try
747                {
748                    realIds.add( schemaManager.getAttributeTypeRegistry().getOidByName( idTrimmed ) );
749                }
750                catch ( Exception e )
751                {
752                    realIds.add( idTrimmed );
753                }
754            }
755    
756            // return nothing
757            if ( noAttribute )
758            {
759                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, DN.EMPTY_DN );
760                return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), searchOperationContext );
761            }
762            
763            // return everything
764            if ( allUserAttributes && allOperationalAttributes )
765            {
766                ServerEntry rootDSE = (ServerEntry)getRootDSE( null ).clone();
767                return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( rootDSE ), searchOperationContext );
768            }
769            
770            ServerEntry serverEntry = new DefaultServerEntry( schemaManager, DN.EMPTY_DN );
771            
772            ServerEntry rootDSE = getRootDSE( new GetRootDSEOperationContext( searchOperationContext.getSession() ) );
773            
774            for ( EntryAttribute attribute:rootDSE )
775            {
776                AttributeType type = schemaManager.lookupAttributeTypeRegistry( attribute.getUpId() );
777                
778                if ( realIds.contains( type.getOid() ) )
779                {
780                    serverEntry.put( attribute );
781                }
782                else if ( allUserAttributes && ( type.getUsage() == UsageEnum.USER_APPLICATIONS ) )
783                {
784                    serverEntry.put( attribute );
785                }
786                else if ( allOperationalAttributes && ( type.getUsage() != UsageEnum.USER_APPLICATIONS ) )
787                {
788                    serverEntry.put( attribute );
789                }
790            }
791    
792            return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), searchOperationContext );
793        }
794        
795    
796        /* (non-Javadoc)
797         * @see org.apache.directory.server.core.partition.PartitionNexus#search(org.apache.directory.server.core.interceptor.context.SearchOperationContext)
798         */
799        public EntryFilteringCursor search( SearchOperationContext opContext ) throws Exception
800        {
801            DN base = opContext.getDn();
802            SearchControls searchCtls = opContext.getSearchControls();
803            ExprNode filter = opContext.getFilter();
804            
805            // TODO since we're handling the *, and + in the EntryFilteringCursor
806            // we may not need this code: we need see if this is actually the 
807            // case and remove this code.
808            if ( base.size() == 0 )
809            {
810                // We are searching from the rootDSE. We have to distinguish three cases :
811                // 1) The scope is OBJECT : we have to return the rootDSE entry, filtered
812                // 2) The scope is ONELEVEL : we have to return all the Namin
813                boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
814                
815                boolean isOnelevelScope = searchCtls.getSearchScope() == SearchControls.ONELEVEL_SCOPE;
816                
817                boolean isSublevelScope = searchCtls.getSearchScope() == SearchControls.SUBTREE_SCOPE;
818                
819                // test for (objectClass=*)
820                boolean isSearchAll = false;
821                
822                // We have to be careful, as we may have a filter which is not a PresenceFilter
823                if ( filter instanceof PresenceNode )
824                {
825                    isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID );
826                }
827        
828                /*
829                 * if basedn is "", filter is "(objectclass=*)" and scope is object
830                 * then we have a request for the rootDSE
831                 */
832                if ( ( filter instanceof PresenceNode)  && isObjectScope && isSearchAll )
833                {
834                    return searchRootDSE( opContext );
835                }
836                else if ( isObjectScope && ( ! isSearchAll ) )
837                {
838                    return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
839                }
840                else if( isOnelevelScope )
841                {
842                    List<EntryFilteringCursor> cursors = new ArrayList<EntryFilteringCursor>();
843                    for ( Partition p : partitions.values() )
844                    {
845                        opContext.setDn( p.getSuffixDn() );
846                        opContext.setScope( SearchScope.OBJECT );
847                        cursors.add( p.search( opContext ) );
848                    }
849                    
850                    return new CursorList( cursors, opContext );
851                }
852                else if ( isSublevelScope )
853                {
854                    List<EntryFilteringCursor> cursors = new ArrayList<EntryFilteringCursor>();
855                    for ( Partition p : partitions.values() )
856                    {
857                        ClonedServerEntry entry = p.lookup( new LookupOperationContext( directoryService.getAdminSession(), p.getSuffixDn() ) );
858                        if( entry != null )
859                        {
860                            Partition backend = getPartition( entry.getDn() );
861                            opContext.setDn( entry.getDn() );
862                            cursors.add( backend.search( opContext ) );
863                        }
864                    }
865                    
866                    // don't feed the above Cursors' list to a BaseEntryFilteringCursor it is skipping the naming context entry of each partition 
867                    return new CursorList( cursors, opContext );
868                }
869        
870                // TODO : handle searches based on the RootDSE
871                throw new LdapNoSuchObjectException();
872            }
873        
874            base.normalize( schemaManager.getNormalizerMapping() );
875            Partition backend = getPartition( base );
876            return backend.search( opContext );
877        }
878    
879    
880        /* (non-Javadoc)
881         * @see org.apache.directory.server.core.partition.PartitionNexus#unbind(org.apache.directory.server.core.interceptor.context.UnbindOperationContext)
882         */
883        public void unbind( UnbindOperationContext unbindContext ) throws Exception
884        {
885            Partition partition = getPartition( unbindContext.getDn() );
886            partition.unbind( unbindContext );
887        }
888    
889    
890        /* (non-Javadoc)
891         * @see org.apache.directory.server.core.partition.PartitionNexus#getRootDSE(org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext)
892         */
893        public ClonedServerEntry getRootDSE( GetRootDSEOperationContext getRootDSEContext )
894        {
895            return new ClonedServerEntry( rootDSE );
896        }
897    
898    
899        /* (non-Javadoc)
900         * @see org.apache.directory.server.core.partition.PartitionNexus#addContextPartition(org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext)
901         */
902        public synchronized void addContextPartition( AddContextPartitionOperationContext opContext ) throws Exception
903        {
904            Partition partition = opContext.getPartition();
905    
906            // Turn on default indices
907            String key = partition.getSuffixDn().getNormName();
908            
909            if ( partitions.containsKey( key ) )
910            {
911                throw new ConfigurationException( I18n.err( I18n.ERR_263, key ) );
912            }
913    
914            if ( ! partition.isInitialized() )
915            {
916                partition.initialize( );
917            }
918            
919            synchronized ( partitionLookupTree )
920            {
921                DN partitionSuffix = partition.getSuffixDn();
922                
923                if ( partitionSuffix == null )
924                {
925                    throw new ConfigurationException( I18n.err( I18n.ERR_267, partition.getId() ) );
926                }
927                
928                partitions.put( partitionSuffix.getNormName(), partition );
929                partitionLookupTree.add( partition.getSuffixDn(), partition );
930    
931                EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
932    
933                if ( namingContexts == null )
934                {
935                    namingContexts = new DefaultServerAttribute( 
936                        schemaManager.lookupAttributeTypeRegistry( SchemaConstants.NAMING_CONTEXTS_AT ), partitionSuffix.getName() );
937                    rootDSE.put( namingContexts );
938                }
939                else
940                {
941                    namingContexts.add( partitionSuffix.getName() );
942                }
943            }
944        }
945    
946    
947        /* (non-Javadoc)
948         * @see org.apache.directory.server.core.partition.PartitionNexus#removeContextPartition(org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext)
949         */
950        public synchronized void removeContextPartition( RemoveContextPartitionOperationContext removeContextPartition ) throws Exception
951        {
952            // Get the Partition name. It's a DN.
953            String key = removeContextPartition.getDn().getNormName();
954            
955            // Retrieve this partition from the aprtition's table
956            Partition partition = partitions.get( key );
957            
958            if ( partition == null )
959            {
960                String msg = I18n.err( I18n.ERR_34, key );
961                LOG.error( msg );
962                throw new NameNotFoundException( msg );
963            }
964            
965            String partitionSuffix = partition.getSuffixDn().getName();
966    
967            // Retrieve the namingContexts from the RootDSE : the partition
968            // suffix must be present in those namingContexts
969            EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
970            
971            if ( namingContexts != null )
972            {
973                if ( namingContexts.contains( partitionSuffix ) )
974                {
975                    namingContexts.remove( partitionSuffix );
976                }
977                else
978                {
979                    String msg = I18n.err( I18n.ERR_35, key );
980                    LOG.error( msg );
981                    throw new NameNotFoundException( msg );
982                }
983            }
984    
985            // Update the partition tree
986            partitionLookupTree.remove( partition );
987            partitions.remove( key );
988            partition.destroy();
989        }
990    
991    
992        /* (non-Javadoc)
993         * @see org.apache.directory.server.core.partition.PartitionNexus#getSystemPartition()
994         */
995        public Partition getSystemPartition()
996        {
997            return system;
998        }
999    
1000    
1001        /* (non-Javadoc)
1002         * @see org.apache.directory.server.core.partition.PartitionNexus#getPartition(org.apache.directory.shared.ldap.name.DN)
1003         */
1004        public Partition getPartition( DN dn ) throws Exception
1005        {
1006            Partition parent = partitionLookupTree.getParentElement( dn );
1007            
1008            if ( parent == null )
1009            {
1010                throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_268, dn ) );
1011            }
1012            else
1013            {
1014                return parent;
1015            }
1016        }
1017    
1018    
1019        /* (non-Javadoc)
1020         * @see org.apache.directory.server.core.partition.PartitionNexus#getMatchedName(org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext)
1021         */
1022        public DN getMatchedName( GetMatchedNameOperationContext matchedNameContext ) throws Exception
1023        {
1024            DN dn = ( DN ) matchedNameContext.getDn().clone();
1025            
1026            while ( dn.size() > 0 )
1027            {
1028                if ( hasEntry( new EntryOperationContext( matchedNameContext.getSession(), dn ) ) )
1029                {
1030                    return dn;
1031                }
1032    
1033                dn.remove( dn.size() - 1 );
1034            }
1035    
1036            return dn;
1037        }
1038    
1039    
1040        /* (non-Javadoc)
1041         * @see org.apache.directory.server.core.partition.PartitionNexus#getSuffix(org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext)
1042         */
1043        public DN getSuffix( GetSuffixOperationContext getSuffixContext ) throws Exception
1044        {
1045            Partition backend = getPartition( getSuffixContext.getDn() );
1046            return backend.getSuffixDn();
1047        }
1048    
1049    
1050        /* (non-Javadoc)
1051         * @see org.apache.directory.server.core.partition.PartitionNexus#listSuffixes(org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext)
1052         */
1053        public Set<String> listSuffixes( ListSuffixOperationContext emptyContext ) throws Exception
1054        {
1055            return Collections.unmodifiableSet( partitions.keySet() );
1056        }
1057    
1058    
1059        /* (non-Javadoc)
1060         * @see org.apache.directory.server.core.partition.PartitionNexus#registerSupportedExtensions(java.util.Set)
1061         */
1062        public void registerSupportedExtensions( Set<String> extensionOids ) throws Exception
1063        {
1064            EntryAttribute supportedExtension = rootDSE.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1065    
1066            if ( supportedExtension == null )
1067            {
1068                rootDSE.set( SchemaConstants.SUPPORTED_EXTENSION_AT );
1069                supportedExtension = rootDSE.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1070            }
1071    
1072            for ( String extensionOid : extensionOids )
1073            {
1074                supportedExtension.add( extensionOid );
1075            }
1076        }
1077    
1078    
1079        /* (non-Javadoc)
1080         * @see org.apache.directory.server.core.partition.PartitionNexus#registerSupportedSaslMechanisms(java.util.Set)
1081         */
1082        public void registerSupportedSaslMechanisms( Set<String> supportedSaslMechanisms ) throws Exception
1083        {
1084            EntryAttribute supportedSaslMechanismsAttribute = rootDSE.get( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1085    
1086            if ( supportedSaslMechanismsAttribute == null )
1087            {
1088                rootDSE.set( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1089                supportedSaslMechanismsAttribute = rootDSE.get( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1090            }
1091    
1092            for ( String saslMechanism : supportedSaslMechanisms )
1093            {
1094                supportedSaslMechanismsAttribute.add( saslMechanism );
1095            }
1096        }
1097    
1098    
1099        /**
1100         * Unregisters an ContextPartition with this BackendManager.  Called for each
1101         * registered Backend right befor it is to be stopped.  This prevents
1102         * protocol server requests from reaching the Backend and effectively puts
1103         * the ContextPartition's naming context offline.
1104         *
1105         * Operations against the naming context should result in an LDAP BUSY
1106         * result code in the returnValue if the naming context is not online.
1107         *
1108         * @param partition ContextPartition component to unregister with this
1109         * BackendNexus.
1110         * @throws Exception if there are problems unregistering the partition
1111         */
1112        private void unregister( Partition partition ) throws Exception
1113        {
1114            EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
1115            
1116            if ( namingContexts != null )
1117            {
1118                namingContexts.remove( partition.getSuffixDn().getName() );
1119            }
1120            
1121            partitions.remove( partition.getSuffixDn().getName() );
1122        }
1123    
1124    
1125        /**
1126         * @return the directoryService
1127         */
1128        public DirectoryService getDirectoryService()
1129        {
1130            return directoryService;
1131        }
1132    
1133    
1134        /**
1135         * @param directoryService the directoryService to set
1136         */
1137        public void setDirectoryService( DirectoryService directoryService )
1138        {
1139            this.directoryService = directoryService;
1140        }
1141    }