View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.shared.partition;
21  
22  
23  import java.io.InputStream;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Properties;
33  import java.util.Set;
34  
35  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
36  import org.apache.directory.api.ldap.model.cursor.CursorException;
37  import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
38  import org.apache.directory.api.ldap.model.cursor.SingletonCursor;
39  import org.apache.directory.api.ldap.model.entry.Attribute;
40  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
41  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
42  import org.apache.directory.api.ldap.model.entry.DefaultModification;
43  import org.apache.directory.api.ldap.model.entry.Entry;
44  import org.apache.directory.api.ldap.model.entry.Modification;
45  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
46  import org.apache.directory.api.ldap.model.entry.Value;
47  import org.apache.directory.api.ldap.model.exception.LdapException;
48  import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
49  import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
50  import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
51  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
52  import org.apache.directory.api.ldap.model.filter.ExprNode;
53  import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
54  import org.apache.directory.api.ldap.model.message.SearchScope;
55  import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
56  import org.apache.directory.api.ldap.model.name.Dn;
57  import org.apache.directory.api.ldap.model.schema.AttributeType;
58  import org.apache.directory.api.ldap.model.schema.AttributeTypeOptions;
59  import org.apache.directory.api.ldap.model.schema.UsageEnum;
60  import org.apache.directory.api.ldap.util.tree.DnNode;
61  import org.apache.directory.api.util.exception.MultiException;
62  import org.apache.directory.server.constants.ServerDNConstants;
63  import org.apache.directory.server.core.api.DirectoryService;
64  import org.apache.directory.server.core.api.InterceptorEnum;
65  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
66  import org.apache.directory.server.core.api.filtering.CursorList;
67  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
68  import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
69  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
70  import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
71  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
72  import org.apache.directory.server.core.api.interceptor.context.GetRootDseOperationContext;
73  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
74  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
75  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
76  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
77  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
78  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
79  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
80  import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext;
81  import org.apache.directory.server.core.api.partition.AbstractPartition;
82  import org.apache.directory.server.core.api.partition.Partition;
83  import org.apache.directory.server.core.api.partition.PartitionNexus;
84  import org.apache.directory.server.core.api.partition.PartitionReadTxn;
85  import org.apache.directory.server.core.api.partition.PartitionTxn;
86  import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
87  import org.apache.directory.server.core.api.partition.Subordinates;
88  import org.apache.directory.server.i18n.I18n;
89  import org.slf4j.Logger;
90  import org.slf4j.LoggerFactory;
91  
92  
93  /**
94   * A root {@link Partition} that contains all other partitions, and
95   * routes all operations to the child partition that matches to its base suffixes.
96   * It also provides some extended operations such as accessing rootDSE and
97   * listing base suffixes.
98   *
99   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
100  */
101 public class DefaultPartitionNexus extends AbstractPartition implements PartitionNexus
102 {
103     /** A logger for this class */
104     private static final Logger LOG = LoggerFactory.getLogger( DefaultPartitionNexus.class );
105 
106     /** the fixed id: 'NEXUS' */
107     private static final String NEXUS_ID = "NEXUS";
108 
109     /** Speedup for logs */
110     private static final boolean IS_DEBUG = LOG.isDebugEnabled();
111 
112     /** the vendorName string proudly set to: Apache Software Foundation*/
113     private static final String ASF = "Apache Software Foundation";
114 
115     /** the read only rootDSE attributes */
116     private final Entry rootDse;
117 
118     /** The DirectoryService instance */
119     private DirectoryService directoryService;
120 
121     /** the partitions keyed by normalized suffix strings */
122     private Map<String, Partition> partitions = new HashMap<>();
123 
124     /** A structure to hold all the partitions */
125     private DnNode<Partition> partitionLookupTree = new DnNode<>();
126 
127     private final List<Modification> mods = new ArrayList<>( 2 );
128 
129     /** The cn=schema Dn */
130     private Dn subschemaSubentryDn;
131 
132 
133     /**
134      * Creates the root nexus singleton of the entire system.  The root DSE has
135      * several attributes that are injected into it besides those that may
136      * already exist.  As partitions are added to the system more namingContexts
137      * attributes are added to the rootDSE.
138      *
139      * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
140      * @param rootDse the root entry for the DSA
141      * @throws LdapException on failure to initialize
142      */
143     public DefaultPartitionNexus( Entry rootDse ) throws LdapException
144     {
145         id = NEXUS_ID;
146         suffixDn = null;
147 
148         // setup that root DSE
149         this.rootDse = rootDse;
150 
151         // Add the basic informations
152         rootDse.put( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, ServerDNConstants.CN_SCHEMA_DN );
153         rootDse.put( SchemaConstants.SUPPORTED_LDAP_VERSION_AT, "3" );
154         rootDse.put( SchemaConstants.SUPPORTED_FEATURES_AT, 
155             SchemaConstants.FEATURE_ALL_OPERATIONAL_ATTRIBUTES,
156             SchemaConstants.FEATURE_MODIFY_INCREMENT );
157         rootDse.put( SchemaConstants.SUPPORTED_EXTENSION_AT, NoticeOfDisconnect.EXTENSION_OID );
158 
159         // Add the objectClasses
160         rootDse.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.EXTENSIBLE_OBJECT_OC );
161 
162         // Add the 'vendor' name and version infos
163         rootDse.put( SchemaConstants.VENDOR_NAME_AT, ASF );
164 
165         Properties props = new Properties();
166 
167         try ( InputStream inputStream = getClass().getResourceAsStream( "version.properties" ) )
168         {
169             props.load( inputStream );
170         }
171         catch ( IOException e )
172         {
173             LOG.error( I18n.err( I18n.ERR_33 ) );
174         }
175 
176         rootDse.put( SchemaConstants.VENDOR_VERSION_AT, props.getProperty( "apacheds.version", "UNKNOWN" ) );
177 
178         // The rootDSE uuid has been randomly created
179         rootDse.put( SchemaConstants.ENTRY_UUID_AT, "f290425c-8272-4e62-8a67-92b06f38dbf5" );
180     }
181 
182 
183     /**
184      * {@inheritDoc}
185      */
186     @Override
187     public void repair() throws LdapException
188     {
189         // Nothing to do
190     }
191 
192 
193     /**
194      * {@inheritDoc}
195      */
196     @Override
197     protected void doRepair() throws LdapException
198     {
199         // Nothing to do
200     }
201 
202 
203     /**
204      * {@inheritDoc}
205      */
206     @Override
207     protected void doInit() throws LdapException
208     {
209         // NOTE: We ignore ContextPartitionConfiguration parameter here.
210         if ( !initialized )
211         {
212             // Add the supported request controls
213             Iterator<String> ctrlOidItr = directoryService.getLdapCodecService().registeredRequestControls();
214 
215             while ( ctrlOidItr.hasNext() )
216             {
217                 rootDse.add( SchemaConstants.SUPPORTED_CONTROL_AT, ctrlOidItr.next() );
218             }
219 
220             // Add the supported response controls
221             ctrlOidItr = directoryService.getLdapCodecService().registeredResponseControls();
222 
223             while ( ctrlOidItr.hasNext() )
224             {
225                 rootDse.add( SchemaConstants.SUPPORTED_CONTROL_AT, ctrlOidItr.next() );
226             }
227 
228             schemaManager = directoryService.getSchemaManager();
229 
230             Value attr = rootDse.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
231             subschemaSubentryDn = directoryService.getDnFactory().create( attr.getString() );
232 
233             List<Partition> initializedPartitions = new ArrayList<>();
234 
235             initializedPartitions.add( 0, directoryService.getSystemPartition() );
236             addContextPartition( directoryService.getSystemPartition() );
237 
238             try
239             {
240                 for ( Partition partition : directoryService.getPartitions() )
241                 {
242                     addContextPartition( partition );
243                     initializedPartitions.add( partition );
244                 }
245 
246                 createContextCsnModList();
247 
248                 initialized = true;
249             }
250             finally
251             {
252                 if ( !initialized )
253                 {
254                     Iterator<Partition> i = initializedPartitions.iterator();
255 
256                     while ( i.hasNext() )
257                     {
258                         Partition partition = i.next();
259                         i.remove();
260 
261                         try
262                         {
263                             partition.destroy( partition.beginReadTransaction() );
264                         }
265                         catch ( Exception e )
266                         {
267                             LOG.warn( "Failed to destroy a partition: " + partition.getSuffixDn(), e );
268                         }
269                         finally
270                         {
271                             unregister( partition );
272                         }
273                     }
274                 }
275             }
276         }
277     }
278 
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
284     protected synchronized void doDestroy( PartitionTxn partitionTxn )
285     {
286         if ( !initialized )
287         {
288             return;
289         }
290 
291         // make sure this loop is not fail fast so all backing stores can
292         // have an attempt at closing down and synching their cached entries
293         for ( String suffix : new HashSet<>( this.partitions.keySet() ) )
294         {
295             try
296             {
297                 removeContextPartition( suffix );
298             }
299             catch ( Exception e )
300             {
301                 LOG.warn( "Failed to destroy a partition: " + suffixDn, e );
302             }
303         }
304 
305         initialized = false;
306     }
307 
308 
309     /**
310      * {@inheritDoc}
311      */
312     @Override
313     public void setId( String id )
314     {
315         throw new UnsupportedOperationException( I18n.err( I18n.ERR_264 ) );
316     }
317 
318 
319     /**
320      * {@inheritDoc}
321      */
322     @Override
323     public void setSuffixDn( Dn suffix )
324     {
325         throw new UnsupportedOperationException();
326     }
327 
328 
329     /**
330      * {@inheritDoc}
331      */
332     @Override
333     public void sync() throws LdapException
334     {
335         MultiException errors = null;
336 
337         for ( Partition partition : this.partitions.values() )
338         {
339             try
340             {
341                 partition.saveContextCsn( partition.beginReadTransaction() );
342                 partition.sync();
343             }
344             catch ( Exception e )
345             {
346                 LOG.warn( "Failed to flush partition data out.", e );
347 
348                 if ( errors == null )
349                 {
350                     //noinspection ThrowableInstanceNeverThrown
351                     errors = new MultiException( I18n.err( I18n.ERR_265 ) );
352                 }
353 
354                 // @todo really need to send this info to a monitor
355                 errors.addThrowable( e );
356             }
357         }
358 
359         if ( errors != null )
360         {
361             throw new LdapOtherException( errors.getMessage(), errors );
362         }
363     }
364 
365 
366     // ------------------------------------------------------------------------
367     // DirectoryPartition Interface Method Implementations
368     // ------------------------------------------------------------------------
369     /**
370      * {@inheritDoc}
371      */
372     @Override
373     public void add( AddOperationContext addContext ) throws LdapException
374     {
375         Partition partition = addContext.getPartition();
376         partition.add( addContext );
377     }
378 
379 
380     /**
381      * {@inheritDoc}
382      */
383     @Override
384     public boolean compare( CompareOperationContext compareContext ) throws LdapException
385     {
386         Attribute attr = compareContext.getOriginalEntry().get( compareContext.getAttributeType() );
387 
388         // complain if the attribute being compared does not exist in the entry
389         if ( attr == null )
390         {
391             throw new LdapNoSuchAttributeException();
392         }
393 
394         // see first if simple match without normalization succeeds
395         if ( attr.contains( compareContext.getValue() ) )
396         {
397             return true;
398         }
399 
400         // now must apply normalization to all values (attr and in request) to compare
401 
402         /*
403          * Get ahold of the normalizer for the attribute and normalize the request
404          * assertion value for comparisons with normalized attribute values.  Loop
405          * through all values looking for a match.
406          */
407         Value reqVal = compareContext.getValue();
408 
409         for ( Value value : attr )
410         {
411             if ( value.equals( reqVal ) )
412             {
413                 return true;
414             }
415         }
416 
417         return false;
418     }
419 
420 
421     /**
422      * {@inheritDoc}
423      */
424     @Override
425     public Entry delete( DeleteOperationContext deleteContext ) throws LdapException
426     {
427         Partition partition = getPartition( deleteContext.getDn() );
428         return partition.delete( deleteContext );
429     }
430 
431 
432     /**
433      * {@inheritDoc}
434      */
435     @Override
436     public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException
437     {
438         Dn dn = hasEntryContext.getDn();
439 
440         if ( IS_DEBUG )
441         {
442             LOG.debug( "Check if Dn '{}' exists.", dn );
443         }
444 
445         if ( dn.isRootDse() )
446         {
447             return true;
448         }
449 
450         Partition partition = getPartition( dn );
451 
452         return partition.hasEntry( hasEntryContext );
453     }
454 
455 
456     /**
457      * {@inheritDoc}
458      */
459     @Override
460     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
461     {
462         Dn dn = lookupContext.getDn();
463 
464         if ( dn.getNormName().equals( subschemaSubentryDn.getNormName() ) )
465         {
466             return new ClonedServerEntry( rootDse.clone() );
467         }
468 
469         // This is for the case we do a lookup on the rootDSE
470         if ( dn.isRootDse() )
471         {
472             return new ClonedServerEntry( rootDse );
473         }
474 
475         Partition partition = getPartition( dn );
476         Entry entry = partition.lookup( lookupContext );
477 
478         if ( entry == null )
479         {
480             throw new LdapNoSuchObjectException( "Attempt to lookup non-existant entry: "
481                 + dn.getName() );
482         }
483 
484         return entry;
485     }
486 
487 
488     /**
489      * {@inheritDoc}
490      */
491     @Override
492     public void modify( ModifyOperationContext modifyContext ) throws LdapException
493     {
494         // Special case : if we don't have any modification to apply, just return
495         if ( modifyContext.getModItems().isEmpty() )
496         {
497             return;
498         }
499 
500         Partition partition = getPartition( modifyContext.getDn() );
501 
502         partition.modify( modifyContext );
503 
504         if ( modifyContext.isPushToEvtInterceptor() )
505         {
506             directoryService.getInterceptor( InterceptorEnum.EVENT_INTERCEPTOR.getName() ).modify( modifyContext );
507         }
508     }
509 
510 
511     /**
512      * {@inheritDoc}
513      */
514     @Override
515     public void move( MoveOperationContext moveContext ) throws LdapException
516     {
517         // Get the current partition
518         Partition partition = getPartition( moveContext.getDn() );
519 
520         partition.move( moveContext );
521     }
522 
523 
524     /**
525      * {@inheritDoc}
526      */
527     @Override
528     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
529     {
530         Partition partition = getPartition( moveAndRenameContext.getDn() );
531         partition.moveAndRename( moveAndRenameContext );
532     }
533 
534 
535     /**
536      * {@inheritDoc}
537      */
538     @Override
539     public void rename( RenameOperationContext renameContext ) throws LdapException
540     {
541         Partition partition = getPartition( renameContext.getDn() );
542         partition.rename( renameContext );
543     }
544 
545 
546     private EntryFilteringCursor searchRootDse( SearchOperationContext searchContext ) throws LdapException
547     {
548         Set<AttributeTypeOptions> ids = searchContext.getReturningAttributes();
549 
550         // -----------------------------------------------------------
551         // If nothing is asked for then we just return the entry asis.
552         // We let other mechanisms filter out operational attributes.
553         // -----------------------------------------------------------
554         if ( ( ids == null ) || ( ids.isEmpty() ) )
555         {
556             return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( getRootDse( null ) ), searchContext,
557                 directoryService.getSchemaManager() );
558         }
559 
560         // -----------------------------------------------------------
561         // Collect all the real attributes besides 1.1, +, and * and
562         // note if we've seen these special attributes as well.
563         // -----------------------------------------------------------
564 
565         Set<String> realIds = new HashSet<>();
566         boolean allUserAttributes = searchContext.isAllUserAttributes();
567         boolean allOperationalAttributes = searchContext.isAllOperationalAttributes();
568         boolean noAttribute = searchContext.isNoAttributes();
569 
570         for ( AttributeTypeOptions id : ids )
571         {
572             try
573             {
574                 realIds.add( id.getAttributeType().getOid() );
575             }
576             catch ( Exception e )
577             {
578                 realIds.add( id.getAttributeType().getName() );
579             }
580         }
581 
582         // return nothing
583         if ( noAttribute )
584         {
585             Entry serverEntry = new DefaultEntry( schemaManager, Dn.ROOT_DSE );
586             return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( serverEntry ), searchContext,
587                 directoryService.getSchemaManager() );
588         }
589 
590         // return everything
591         if ( allUserAttributes && allOperationalAttributes )
592         {
593             Entry foundRootDse = getRootDse( null );
594             return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( foundRootDse ), searchContext,
595                 directoryService.getSchemaManager() );
596         }
597 
598         Entry serverEntry = new DefaultEntry( schemaManager, Dn.ROOT_DSE );
599         GetRootDseOperationContextontext/GetRootDseOperationContext.html#GetRootDseOperationContext">GetRootDseOperationContext getRootDseContext = new GetRootDseOperationContext( searchContext.getSession() );
600         getRootDseContext.setPartition( searchContext.getPartition() );
601         getRootDseContext.setTransaction( searchContext.getTransaction() );
602 
603         Entry foundRootDse = getRootDse( getRootDseContext );
604 
605         for ( Attribute attribute : foundRootDse )
606         {
607             AttributeType type = schemaManager.lookupAttributeTypeRegistry( attribute.getId() );
608 
609             if ( realIds.contains( type.getOid() )
610                     || ( allUserAttributes && ( type.getUsage() == UsageEnum.USER_APPLICATIONS ) )
611                     || ( allOperationalAttributes && ( type.getUsage() != UsageEnum.USER_APPLICATIONS ) ) )
612             {
613                 serverEntry.put( attribute );
614             }
615         }
616 
617         return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( serverEntry ), searchContext,
618             directoryService.getSchemaManager() );
619     }
620 
621 
622     /**
623      * {@inheritDoc}
624      */
625     @Override
626     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
627     {
628         Dn baseDn = searchContext.getDn();
629 
630         // TODO since we're handling the *, and + in the EntryFilteringCursor
631         // we may not need this code: we need see if this is actually the
632         // case and remove this code.
633         if ( baseDn.size() == 0 )
634         {
635             return searchFromRoot( searchContext );
636         }
637 
638         // Not sure we need this code...
639         if ( !baseDn.isSchemaAware() )
640         {
641             searchContext.setDn( new Dn( schemaManager, baseDn ) );
642         }
643 
644         // Normal case : do a search on the specific partition
645         Partition backend = searchContext.getPartition();
646 
647         return backend.search( searchContext );
648     }
649 
650 
651     /**
652      * Do a search from the root of the DIT. We have a few use cases to consider :
653      * A) The scope is OBJECT
654      * If the filter is (ObjectClass = *), then this is a RootDSE fetch, otherwise, we just
655      * return nothing.
656      * B) The scope is ONELEVEL
657      * We just return the contextEntries of all the existing partitions
658      * C) The scope is SUBLEVEL :
659      * In this case, we have to do a search in each of the existing partition. We will get
660      * back a list of cursors and we will wrap this list in the resulting EntryFilteringCursor.
661      *
662      * @param searchContext
663      * @return
664      * @throws LdapException
665      */
666     private EntryFilteringCursor searchFromRoot( SearchOperationContext searchContext )
667         throws LdapException
668     {
669         ExprNode filter = searchContext.getFilter();
670 
671         // We are searching from the rootDSE. We have to distinguish three cases :
672         // 1) The scope is OBJECT : we have to return the rootDSE entry, filtered
673         // 2) The scope is ONELEVEL : we have to return all the Naming Contexts
674         boolean isObjectScope = searchContext.getScope() == SearchScope.OBJECT;
675 
676         boolean isOnelevelScope = searchContext.getScope() == SearchScope.ONELEVEL;
677 
678         // test for (objectClass=*)
679         boolean isSearchAll = false;
680 
681         // We have to be careful, as we may have a filter which is not a PresenceFilter
682         if ( filter instanceof ObjectClassNode )
683         {
684             isSearchAll = true;
685         }
686 
687         if ( isObjectScope )
688         {
689             if ( isSearchAll )
690             {
691                 // if basedn is "", filter is "(objectclass=*)" and scope is object
692                 // then we have a request for the rootDSE
693                 return searchRootDse( searchContext );
694             }
695             else
696             {
697                 // Nothing to return in this case
698                 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext,
699                     directoryService.getSchemaManager() );
700             }
701         }
702         else if ( isOnelevelScope )
703         {
704             // Loop on all the partitions
705             // We will look into all the partitions, thus we create a list of cursors.
706             List<EntryFilteringCursor> cursors = new ArrayList<>();
707 
708             for ( Partition partition : partitions.values() )
709             {
710                 Dn contextDn = partition.getSuffixDn();
711                 PartitionTxn partitionTxn = partition.beginReadTransaction();
712                 HasEntryOperationContextor/context/HasEntryOperationContext.html#HasEntryOperationContext">HasEntryOperationContext hasEntryContext = new HasEntryOperationContext(
713                     searchContext.getSession(), contextDn );
714                 hasEntryContext.setPartition( partition );
715                 hasEntryContext.setTransaction( partitionTxn );
716                 searchContext.setPartition( partition );
717                 searchContext.setTransaction( partitionTxn );
718 
719                 // search only if the context entry exists
720                 if ( partition.hasEntry( hasEntryContext ) )
721                 {
722                     searchContext.setDn( contextDn );
723                     searchContext.setScope( SearchScope.OBJECT );
724                     cursors.add( partition.search( searchContext ) );
725                 }
726             }
727 
728             return new CursorList( cursors, searchContext );
729         }
730         else
731         {
732             // This is a SUBLEVEL search. We will do multiple searches and wrap
733             // a CursorList into the EntryFilteringCursor
734             List<EntryFilteringCursor> cursors = new ArrayList<>();
735 
736             for ( Partition partition : partitions.values() )
737             {
738                 PartitionTxn partitionTxn = partition.beginReadTransaction();
739                 Dn contextDn = partition.getSuffixDn();
740                 HasEntryOperationContextor/context/HasEntryOperationContext.html#HasEntryOperationContext">HasEntryOperationContext hasEntryContext = new HasEntryOperationContext(
741                     searchContext.getSession(), contextDn );
742                 hasEntryContext.setPartition( partition );
743                 hasEntryContext.setTransaction( partitionTxn );
744                 searchContext.setPartition( partition );
745                 searchContext.setTransaction( partitionTxn );
746 
747                 if ( partition.hasEntry( hasEntryContext ) )
748                 {
749                     searchContext.setDn( contextDn );
750                     EntryFilteringCursor cursor = partition.search( searchContext );
751 
752                     try
753                     {
754                         if ( cursor.first() )
755                         {
756                             cursor.beforeFirst();
757                             cursors.add( cursor );
758                         }
759                     }
760                     catch ( CursorException e )
761                     {
762                         // Do nothing
763                     }
764                 }
765             }
766 
767             // don't feed the above Cursors' list to a BaseEntryFilteringCursor it is skipping the naming context entry of each partition
768             if ( cursors.isEmpty() )
769             {
770                 // No candidate, return an emtpy cursor
771                 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext,
772                     directoryService.getSchemaManager() );
773             }
774             else
775             {
776                 return new CursorList( cursors, searchContext );
777             }
778         }
779     }
780 
781 
782     /**
783      * {@inheritDoc}
784      */
785     @Override
786     public void unbind( UnbindOperationContext unbindContext ) throws LdapException
787     {
788         Dn unbindContextDn = unbindContext.getDn();
789 
790         if ( !Dn.isNullOrEmpty( unbindContextDn ) )
791         {
792             Partition partition = getPartition( unbindContext.getDn() );
793             partition.unbind( unbindContext );
794         }
795     }
796 
797 
798     /**
799      * {@inheritDoc}
800      */
801     @Override
802     public Entry getRootDse( GetRootDseOperationContext getRootDseContext )
803     {
804         return rootDse.clone();
805     }
806 
807 
808     /**
809      * {@inheritDoc}
810      */
811     @Override
812     public Value getRootDseValue( AttributeType attributeType )
813     {
814         return rootDse.get( attributeType ).get();
815     }
816 
817 
818     /**
819      * {@inheritDoc}
820      */
821     @Override
822     public synchronized void addContextPartition( Partition partition ) throws LdapException
823     {
824         // Turn on default indices
825         String key = partition.getSuffixDn().getNormName();
826 
827         if ( partitions.containsKey( key ) )
828         {
829             throw new LdapOtherException( I18n.err( I18n.ERR_263, key ) );
830         }
831 
832         if ( !partition.isInitialized() )
833         {
834             partition.initialize();
835         }
836 
837         synchronized ( partitionLookupTree )
838         {
839             Dn partitionSuffix = partition.getSuffixDn();
840 
841             if ( partitionSuffix == null )
842             {
843                 throw new LdapOtherException( I18n.err( I18n.ERR_267, partition.getId() ) );
844             }
845 
846             partitions.put( partitionSuffix.getNormName(), partition );
847             partitionLookupTree.add( partition.getSuffixDn(), partition );
848 
849             Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT );
850 
851             if ( namingContexts == null )
852             {
853                 namingContexts = new DefaultAttribute( schemaManager
854                     .lookupAttributeTypeRegistry( SchemaConstants.NAMING_CONTEXTS_AT ), partitionSuffix.getName() );
855                 rootDse.put( namingContexts );
856             }
857             else
858             {
859                 namingContexts.add( partitionSuffix.getName() );
860             }
861         }
862     }
863 
864 
865     /**
866      * {@inheritDoc}
867      */
868     @Override
869     public synchronized void removeContextPartition( String partitionDn )
870         throws LdapException
871     {
872         // Retrieve this partition from the aprtition's table
873         Partition partition = partitions.get( partitionDn );
874 
875         if ( partition == null )
876         {
877             String msg = I18n.err( I18n.ERR_34, partitionDn );
878             LOG.error( msg );
879             throw new LdapNoSuchObjectException( msg );
880         }
881 
882         String partitionSuffix = partition.getSuffixDn().getNormName();
883 
884         // Retrieve the namingContexts from the RootDSE : the partition
885         // suffix must be present in those namingContexts
886         Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT );
887 
888         if ( namingContexts != null )
889         {
890             Value foundNC = null;
891 
892             for ( Value namingContext : namingContexts )
893             {
894                 String normalizedNC = new Dn( schemaManager, namingContext.getString() ).getNormName();
895 
896                 if ( partitionSuffix.equals( normalizedNC ) )
897                 {
898                     foundNC = namingContext;
899                     break;
900                 }
901             }
902 
903             if ( foundNC != null )
904             {
905                 namingContexts.remove( foundNC );
906             }
907             else
908             {
909                 String msg = I18n.err( I18n.ERR_35, partitionDn );
910                 LOG.error( msg );
911                 throw new LdapNoSuchObjectException( msg );
912             }
913         }
914 
915         // Update the partition tree
916         synchronized ( partitionLookupTree )
917         {
918             partitionLookupTree.remove( partition.getSuffixDn() );
919         }
920 
921         partitions.remove( partitionDn );
922 
923         try
924         {
925             partition.destroy( partition.beginReadTransaction() );
926         }
927         catch ( Exception e )
928         {
929             throw new LdapOperationErrorException( e.getMessage(), e );
930         }
931     }
932 
933 
934     /**
935      * {@inheritDoc}
936      */
937     @Override
938     public Partition getPartition( Dn dn ) throws LdapException
939     {
940         Partition parent;
941 
942         if ( dn == null )
943         {
944             dn = Dn.ROOT_DSE;
945         }
946 
947         if ( !dn.isSchemaAware() )
948         {
949             dn = new Dn( schemaManager, dn );
950         }
951 
952         if ( dn.isRootDse() || dn.getNormName().equals( subschemaSubentryDn.getNormName() ) )
953         {
954             return new RootPartition( schemaManager );
955         }
956 
957         synchronized ( partitionLookupTree )
958         {
959             parent = partitionLookupTree.getElement( dn );
960         }
961 
962         if ( parent == null )
963         {
964             throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_268, dn ) );
965         }
966         else
967         {
968             return parent;
969         }
970     }
971 
972 
973     /**
974      * {@inheritDoc}
975      */
976     @Override
977     public Dn getSuffixDn( Dn dn ) throws LdapException
978     {
979         Partition partition = getPartition( dn );
980 
981         return partition.getSuffixDn();
982     }
983 
984 
985     /* (non-Javadoc)
986      */
987     @Override
988     public Set<String> listSuffixes() throws LdapException
989     {
990         return Collections.unmodifiableSet( partitions.keySet() );
991     }
992 
993 
994     /**
995      * {@inheritDoc}
996      */
997     @Override
998     public void registerSupportedExtensions( Set<String> extensionOids ) throws LdapException
999     {
1000         Attribute supportedExtension = rootDse.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1001 
1002         if ( supportedExtension == null )
1003         {
1004             rootDse.put( SchemaConstants.SUPPORTED_EXTENSION_AT, ( String ) null );
1005             supportedExtension = rootDse.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1006         }
1007 
1008         for ( String extensionOid : extensionOids )
1009         {
1010             supportedExtension.add( extensionOid );
1011         }
1012     }
1013 
1014 
1015     /**
1016      * {@inheritDoc}
1017      */
1018     @Override
1019     public void registerSupportedSaslMechanisms( Set<String> supportedSaslMechanisms ) throws LdapException
1020     {
1021         Attribute supportedSaslMechanismsAt;
1022 
1023         supportedSaslMechanismsAt = new DefaultAttribute(
1024             schemaManager.lookupAttributeTypeRegistry( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT ) );
1025 
1026         for ( String saslMechanism : supportedSaslMechanisms )
1027         {
1028             supportedSaslMechanismsAt.add( saslMechanism );
1029         }
1030 
1031         rootDse.add( supportedSaslMechanismsAt );
1032     }
1033 
1034 
1035     /**
1036      * Unregisters an ContextPartition with this BackendManager.  Called for each
1037      * registered Backend right befor it is to be stopped.  This prevents
1038      * protocol server requests from reaching the Backend and effectively puts
1039      * the ContextPartition's naming context offline.
1040      *
1041      * Operations against the naming context should result in an LDAP BUSY
1042      * result code in the returnValue if the naming context is not online.
1043      *
1044      * @param partition ContextPartition component to unregister with this
1045      * BackendNexus.
1046      * @throws Exception if there are problems unregistering the partition
1047      */
1048     private void unregister( Partition partition )
1049     {
1050         Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT );
1051 
1052         if ( namingContexts != null )
1053         {
1054             namingContexts.remove( partition.getSuffixDn().getName() );
1055         }
1056 
1057         partitions.remove( partition.getSuffixDn().getName() );
1058     }
1059 
1060 
1061     /**
1062      * @return the directoryService
1063      */
1064     public DirectoryService getDirectoryService()
1065     {
1066         return directoryService;
1067     }
1068 
1069 
1070     /**
1071      * @param directoryService the directoryService to set
1072      */
1073     public void setDirectoryService( DirectoryService directoryService )
1074     {
1075         this.directoryService = directoryService;
1076     }
1077 
1078 
1079     private void createContextCsnModList() throws LdapException
1080     {
1081         Modification contextCsnMod = new DefaultModification();
1082         contextCsnMod.setOperation( ModificationOperation.REPLACE_ATTRIBUTE );
1083         DefaultAttribute contextCsnAt = new DefaultAttribute( schemaManager
1084             .lookupAttributeTypeRegistry( SchemaConstants.CONTEXT_CSN_AT ) );
1085         contextCsnMod.setAttribute( contextCsnAt );
1086 
1087         mods.add( contextCsnMod );
1088 
1089         Modification timeStampMod = new DefaultModification();
1090         timeStampMod.setOperation( ModificationOperation.REPLACE_ATTRIBUTE );
1091         DefaultAttribute timeStampAt = new DefaultAttribute( schemaManager
1092             .lookupAttributeTypeRegistry( SchemaConstants.MODIFY_TIMESTAMP_AT ) );
1093         timeStampMod.setAttribute( timeStampAt );
1094 
1095         mods.add( timeStampMod );
1096     }
1097 
1098 
1099     /**
1100      * {@inheritDoc}
1101      */
1102     @Override
1103     public String getContextCsn( PartitionTxn partitionTxn )
1104     {
1105         // nexus doesn't contain a contextCSN
1106         return null;
1107     }
1108 
1109 
1110     @Override
1111     public void saveContextCsn( PartitionTxn partitionTxn ) throws LdapException
1112     {
1113     }
1114 
1115 
1116     /**
1117      * Return the number of children and subordinates for a given entry
1118      *
1119      * @param partitionTxn The Partition transaction
1120      * @param entry The entry for which we want to find the subordinates
1121      * @return The Subordinate instance that contains the values.
1122      * @throws LdapException If we had an issue while processing the request
1123      */
1124     @Override
1125     public Subordinates getSubordinates( PartitionTxn partitionTxn, Entry entry ) throws LdapException
1126     {
1127         return new Subordinates();
1128     }
1129 
1130 
1131     @Override
1132     public PartitionReadTxn beginReadTransaction()
1133     {
1134         return new PartitionReadTxn();
1135     }
1136 
1137 
1138     @Override
1139     public PartitionWriteTxn beginWriteTransaction()
1140     {
1141         return new PartitionWriteTxn();
1142     }
1143 }