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 */
020package org.apache.directory.server.core.shared.partition;
021
022
023import java.io.InputStream;
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.Properties;
033import java.util.Set;
034
035import org.apache.directory.api.ldap.model.constants.SchemaConstants;
036import org.apache.directory.api.ldap.model.cursor.CursorException;
037import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
038import org.apache.directory.api.ldap.model.cursor.SingletonCursor;
039import org.apache.directory.api.ldap.model.entry.Attribute;
040import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
041import org.apache.directory.api.ldap.model.entry.DefaultEntry;
042import org.apache.directory.api.ldap.model.entry.DefaultModification;
043import org.apache.directory.api.ldap.model.entry.Entry;
044import org.apache.directory.api.ldap.model.entry.Modification;
045import org.apache.directory.api.ldap.model.entry.ModificationOperation;
046import org.apache.directory.api.ldap.model.entry.Value;
047import org.apache.directory.api.ldap.model.exception.LdapException;
048import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
049import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
050import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
051import org.apache.directory.api.ldap.model.exception.LdapOtherException;
052import org.apache.directory.api.ldap.model.filter.ExprNode;
053import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
054import org.apache.directory.api.ldap.model.message.SearchScope;
055import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
056import org.apache.directory.api.ldap.model.name.Dn;
057import org.apache.directory.api.ldap.model.schema.AttributeType;
058import org.apache.directory.api.ldap.model.schema.AttributeTypeOptions;
059import org.apache.directory.api.ldap.model.schema.UsageEnum;
060import org.apache.directory.api.ldap.util.tree.DnNode;
061import org.apache.directory.api.util.exception.MultiException;
062import org.apache.directory.server.constants.ServerDNConstants;
063import org.apache.directory.server.core.api.DirectoryService;
064import org.apache.directory.server.core.api.InterceptorEnum;
065import org.apache.directory.server.core.api.entry.ClonedServerEntry;
066import org.apache.directory.server.core.api.filtering.CursorList;
067import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
068import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
069import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
070import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
071import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
072import org.apache.directory.server.core.api.interceptor.context.GetRootDseOperationContext;
073import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
074import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
075import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
076import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
077import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
078import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
079import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
080import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext;
081import org.apache.directory.server.core.api.partition.AbstractPartition;
082import org.apache.directory.server.core.api.partition.Partition;
083import org.apache.directory.server.core.api.partition.PartitionNexus;
084import org.apache.directory.server.core.api.partition.PartitionReadTxn;
085import org.apache.directory.server.core.api.partition.PartitionTxn;
086import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
087import org.apache.directory.server.core.api.partition.Subordinates;
088import org.apache.directory.server.i18n.I18n;
089import org.slf4j.Logger;
090import org.slf4j.LoggerFactory;
091
092
093/**
094 * A root {@link Partition} that contains all other partitions, and
095 * routes all operations to the child partition that matches to its base suffixes.
096 * It also provides some extended operations such as accessing rootDSE and
097 * listing base suffixes.
098 *
099 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
100 */
101public 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        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                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                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}