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.subtree;
021
022
023import java.util.ArrayList;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027
028import javax.naming.directory.SearchControls;
029
030import org.apache.directory.api.ldap.model.constants.SchemaConstants;
031import org.apache.directory.api.ldap.model.entry.Attribute;
032import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
033import org.apache.directory.api.ldap.model.entry.DefaultEntry;
034import org.apache.directory.api.ldap.model.entry.DefaultModification;
035import org.apache.directory.api.ldap.model.entry.Entry;
036import org.apache.directory.api.ldap.model.entry.Modification;
037import org.apache.directory.api.ldap.model.entry.ModificationOperation;
038import org.apache.directory.api.ldap.model.entry.Value;
039import org.apache.directory.api.ldap.model.exception.LdapException;
040import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
041import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
042import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
043import org.apache.directory.api.ldap.model.exception.LdapOperationException;
044import org.apache.directory.api.ldap.model.exception.LdapOtherException;
045import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
046import org.apache.directory.api.ldap.model.filter.EqualityNode;
047import org.apache.directory.api.ldap.model.filter.ExprNode;
048import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
049import org.apache.directory.api.ldap.model.filter.PresenceNode;
050import org.apache.directory.api.ldap.model.message.AliasDerefMode;
051import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
052import org.apache.directory.api.ldap.model.message.SearchScope;
053import org.apache.directory.api.ldap.model.message.controls.Subentries;
054import org.apache.directory.api.ldap.model.name.Dn;
055import org.apache.directory.api.ldap.model.schema.AttributeType;
056import org.apache.directory.api.ldap.model.subtree.AdministrativeRole;
057import org.apache.directory.api.ldap.model.subtree.Subentry;
058import org.apache.directory.api.ldap.model.subtree.SubtreeSpecification;
059import org.apache.directory.api.ldap.model.subtree.SubtreeSpecificationParser;
060import org.apache.directory.server.constants.ApacheSchemaConstants;
061import org.apache.directory.server.core.api.CoreSession;
062import org.apache.directory.server.core.api.DirectoryService;
063import org.apache.directory.server.core.api.InterceptorEnum;
064import org.apache.directory.server.core.api.entry.ClonedServerEntry;
065import org.apache.directory.server.core.api.filtering.EntryFilter;
066import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
067import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
068import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
069import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
070import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
071import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
072import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
073import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
074import org.apache.directory.server.core.api.interceptor.context.OperationContext;
075import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
076import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
077import org.apache.directory.server.core.api.partition.Partition;
078import org.apache.directory.server.core.api.partition.PartitionNexus;
079import org.apache.directory.server.core.api.subtree.SubentryCache;
080import org.apache.directory.server.core.api.subtree.SubtreeEvaluator;
081import org.apache.directory.server.i18n.I18n;
082import org.slf4j.Logger;
083import org.slf4j.LoggerFactory;
084
085
086/**
087 * The Subentry interceptor service which is responsible for filtering
088 * out subentries on search operations and injecting operational attributes
089 *
090 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
091 */
092public class SubentryInterceptor extends BaseInterceptor
093{
094    /** The logger for this class */
095    private static final Logger LOG = LoggerFactory.getLogger( SubentryInterceptor.class );
096
097    /** the subentry control OID */
098    private static final String SUBENTRY_CONTROL = Subentries.OID;
099
100    private Value subentryOC;
101
102    /** The SubTree specification parser instance */
103    private SubtreeSpecificationParser ssParser;
104
105    /** A reference to the nexus for direct backend operations */
106    private PartitionNexus nexus;
107
108    /** An enum used for the entries update */
109    private enum OperationEnum
110    {
111        ADD,
112        REMOVE,
113        REPLACE
114    }
115
116
117    /**
118     * Creates a new instance of SubentryInterceptor
119     */
120    public SubentryInterceptor()
121    {
122        super( InterceptorEnum.SUBENTRY_INTERCEPTOR );
123    }
124
125    //-------------------------------------------------------------------------------------------
126    // Search filter methods
127    //-------------------------------------------------------------------------------------------
128    /**
129     * SearchResultFilter used to filter out subentries based on objectClass values.
130     */
131    private class HideSubentriesFilter implements EntryFilter
132    {
133        /**
134         * {@inheritDoc}
135         */
136        @Override
137        public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException
138        {
139            // See if the requested entry is a subentry
140            if ( directoryService.getSubentryCache().hasSubentry( entry.getDn() ) )
141            {
142                return false;
143            }
144
145            // see if we can use objectclass if present
146            return !entry.contains( directoryService.getAtProvider().getObjectClass(), subentryOC );
147        }
148
149
150        /**
151         * {@inheritDoc}
152         */
153        @Override
154        public String toString( String tabs )
155        {
156            return tabs + "HideSubentriesFilter";
157        }
158    }
159
160    /**
161     * SearchResultFilter used to filter out normal entries but shows subentries based on
162     * objectClass values.
163     */
164    private class HideEntriesFilter implements EntryFilter
165    {
166        /**
167         * {@inheritDoc}
168         */
169        @Override
170        public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException
171        {
172            // See if the requested entry is a subentry
173            if ( directoryService.getSubentryCache().hasSubentry( entry.getDn() ) )
174            {
175                return true;
176            }
177
178            // see if we can use objectclass if present
179            return entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC );
180        }
181
182
183        /**
184         * {@inheritDoc}
185         */
186        @Override
187        public String toString( String tabs )
188        {
189            return tabs + "HideEntriesFilter";
190        }
191    }
192
193
194    //-------------------------------------------------------------------------------------------
195    // Interceptor initialization
196    //-------------------------------------------------------------------------------------------
197    /**
198     * Initialize the Subentry Interceptor
199     *
200     * @param directoryService The DirectoryService instance
201     */
202    @Override
203    public void init( DirectoryService directoryService ) throws LdapException
204    {
205        super.init( directoryService );
206
207        nexus = directoryService.getPartitionNexus();
208
209        ssParser = new SubtreeSpecificationParser( schemaManager );
210        AttributeType ocAt = directoryService.getAtProvider().getObjectClass();
211
212        // prepare to find all subentries in all namingContexts
213        Set<String> suffixes = nexus.listSuffixes();
214        ExprNode filter = new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.SUBENTRY_OC ) );
215        SearchControls controls = new SearchControls();
216        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
217        controls.setReturningAttributes( new String[]
218            { SchemaConstants.SUBTREE_SPECIFICATION_AT, SchemaConstants.OBJECT_CLASS_AT } );
219
220        subentryOC = new Value( ocAt, SchemaConstants.SUBENTRY_OC );
221
222        // search each namingContext for subentries
223        for ( String suffix : suffixes )
224        {
225            CoreSession adminSession = directoryService.getAdminSession();
226
227            Dn suffixDn = dnFactory.create( suffix );
228            Partition partition = nexus.getPartition( suffixDn );
229
230            SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, suffixDn, filter,
231                controls );
232            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
233            searchOperationContext.setPartition( partition );
234            searchOperationContext.setTransaction( partition.beginReadTransaction() );
235
236            EntryFilteringCursor subentries = nexus.search( searchOperationContext );
237
238            // Loop on all the found Subentries, parse the SubtreeSpecification
239            // and store the subentry in the subrentry cache
240            try
241            {
242                while ( subentries.next() )
243                {
244                    Entry subentry = subentries.get();
245                    Dn subentryDn = subentry.getDn();
246
247                    String subtree = subentry.get( directoryService.getAtProvider().getSubtreeSpecification() )
248                        .getString();
249                    SubtreeSpecification ss;
250
251                    try
252                    {
253                        ss = ssParser.parse( subtree );
254                    }
255                    catch ( Exception e )
256                    {
257                        LOG.warn( "Failed while parsing subtreeSpecification for {}", subentryDn );
258                        continue;
259                    }
260
261                    Subentry newSubentry = new Subentry();
262
263                    newSubentry.setAdministrativeRoles( getSubentryAdminRoles( subentry ) );
264                    newSubentry.setSubtreeSpecification( ss );
265
266                    directoryService.getSubentryCache().addSubentry( subentryDn, newSubentry );
267                }
268            }
269            catch ( Exception e )
270            {
271                throw new LdapOperationException( e.getMessage(), e );
272            }
273            finally
274            {
275                try
276                {
277                    subentries.close();
278                }
279                catch ( Exception e )
280                {
281                    LOG.error( I18n.err( I18n.ERR_168 ), e );
282                }
283            }
284        }
285    }
286
287
288    //-------------------------------------------------------------------------------------------
289    // Helper methods
290    //-------------------------------------------------------------------------------------------
291    /**
292     * Return the list of AdministrativeRole for a subentry
293     */
294    private Set<AdministrativeRole> getSubentryAdminRoles( Entry subentry ) throws LdapException
295    {
296        Set<AdministrativeRole> adminRoles = new HashSet<>();
297
298        Attribute oc = subentry.get( directoryService.getAtProvider().getObjectClass() );
299
300        if ( oc == null )
301        {
302            throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_305 ) );
303        }
304
305        if ( oc.contains( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) )
306        {
307            adminRoles.add( AdministrativeRole.AccessControlInnerArea );
308        }
309
310        if ( oc.contains( SchemaConstants.SUBSCHEMA_OC ) )
311        {
312            adminRoles.add( AdministrativeRole.SubSchemaSpecificArea );
313        }
314
315        if ( oc.contains( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) )
316        {
317            adminRoles.add( AdministrativeRole.CollectiveAttributeSpecificArea );
318        }
319
320        if ( oc.contains( ApacheSchemaConstants.TRIGGER_EXECUTION_SUBENTRY_OC ) )
321        {
322            adminRoles.add( AdministrativeRole.TriggerExecutionInnerArea );
323        }
324
325        return adminRoles;
326    }
327
328
329    /**
330     * Checks to see if subentries for the search and list operations should be
331     * made visible based on the availability of the search request control
332     *
333     * @param opContext the invocation object to use for determining subentry visibility
334     * @return true if subentries should be visible, false otherwise
335     */
336    private boolean isSubentryVisible( OperationContext opContext )
337    {
338        if ( !opContext.hasRequestControls() )
339        {
340            return false;
341        }
342
343        // found the subentry request control so we return its value
344        if ( opContext.hasRequestControl( SUBENTRY_CONTROL ) )
345        {
346            Subentries subentries = ( Subentries ) opContext.getRequestControl( SUBENTRY_CONTROL );
347            return subentries.isVisible();
348        }
349
350        return false;
351    }
352
353
354    /**
355     * Update all the entries under an AP adding the
356     */
357    private void updateEntries( OperationContext opContext, OperationEnum operation, 
358        Dn apDn, SubtreeSpecification ss, Dn baseDn, List<Attribute> operationalAttributes ) throws LdapException
359    {
360        ExprNode filter = ObjectClassNode.OBJECT_CLASS_NODE; // (objectClass=*)
361        SearchControls controls = new SearchControls();
362        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
363        controls.setReturningAttributes( new String[]
364            { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
365
366        SearchOperationContext searchOperationContext = new SearchOperationContext( opContext.getSession(),
367            baseDn, filter, controls );
368        searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
369        searchOperationContext.setPartition( opContext.getPartition() );
370        searchOperationContext.setTransaction( opContext.getTransaction() );
371
372        EntryFilteringCursor subentries = nexus.search( searchOperationContext );
373
374        try
375        {
376            while ( subentries.next() )
377            {
378                Entry candidate = subentries.get();
379                Dn candidateDn = candidate.getDn();
380
381                if ( directoryService.getEvaluator().evaluate( ss, apDn, candidateDn, candidate ) )
382                {
383                    List<Modification> modifications = null;
384
385                    switch ( operation )
386                    {
387                        case ADD:
388                            modifications = getOperationalModsForAdd( candidate, operationalAttributes );
389                            break;
390
391                        case REMOVE:
392                            modifications = getOperationalModsForRemove( opContext.getDn(), candidate );
393                            break;
394
395                        case REPLACE:
396                            // TODO: why is that commented out???
397                            //modifications = getOperationalModsForReplace( subentryDn, candidate );
398                            break;
399
400                        default:
401                            throw new IllegalArgumentException( "Unexpected operation " + operation );
402                    }
403
404                    LOG.debug( "The entry {} has been evaluated to true for subentry {}", candidate.getDn(), opContext.getDn() );
405                    ModifyOperationContext modifyContext = new ModifyOperationContext( opContext.getSession(), candidateDn, modifications );
406                    modifyContext.setPartition( opContext.getPartition() );
407                    modifyContext.setTransaction( opContext.getTransaction() );
408                    
409                    nexus.modify( modifyContext );
410                }
411            }
412
413            subentries.close();
414        }
415        catch ( Exception e )
416        {
417            throw new LdapOtherException( e.getMessage(), e );
418        }
419        finally
420        {
421            try
422            {
423                subentries.close();
424            }
425            catch ( Exception e )
426            {
427                LOG.error( I18n.err( I18n.ERR_168 ), e );
428            }
429        }
430    }
431
432
433    /**
434     * Checks if the given Dn is a namingContext
435     */
436    private boolean isNamingContext( Dn dn ) throws LdapException
437    {
438        Dn namingContext = nexus.getSuffixDn( dn );
439
440        return dn.equals( namingContext );
441    }
442
443
444    /**
445     * Get the administrativePoint role
446     */
447    private void checkAdministrativeRole( OperationContext opContext, Dn apDn ) throws LdapException
448    {
449        CoreSession session = opContext.getSession();
450        LookupOperationContext lookupContext = new LookupOperationContext( session, apDn,
451            SchemaConstants.ALL_ATTRIBUTES_ARRAY );
452        lookupContext.setPartition( opContext.getPartition() );
453        lookupContext.setTransaction( opContext.getTransaction() );
454
455        Entry administrationPoint = directoryService.getPartitionNexus().lookup( lookupContext );
456
457        // The administrativeRole AT must exist and not be null
458        Attribute administrativeRole = administrationPoint.get( directoryService.getAtProvider()
459            .getAdministrativeRole() );
460
461        // check that administrativeRole has something valid in it for us
462        if ( ( administrativeRole == null ) || ( administrativeRole.size() <= 0 ) )
463        {
464            LOG.error( "The entry on {} is not an AdministrativePoint", apDn );
465            throw new LdapNoSuchAttributeException( I18n.err( I18n.ERR_306, apDn ) );
466        }
467    }
468
469
470    /**
471     * Get the SubtreeSpecification, parse it and stores it into the subentry
472     */
473    private void setSubtreeSpecification( Subentry subentry, Entry entry ) throws LdapException
474    {
475        String subtree = entry.get( directoryService.getAtProvider().getSubtreeSpecification() ).getString();
476        SubtreeSpecification ss;
477
478        try
479        {
480            ss = ssParser.parse( subtree );
481        }
482        catch ( Exception e )
483        {
484            String msg = I18n.err( I18n.ERR_307, entry.getDn() );
485            LOG.warn( msg );
486            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg );
487        }
488
489        subentry.setSubtreeSpecification( ss );
490    }
491
492
493    /**
494     * Checks to see if an entry being renamed has a descendant that is an
495     * administrative point.
496     *
497     * @param name the name of the entry which is used as the search base
498     * @return true if name is an administrative point or one of its descendants
499     * are, false otherwise
500     * @throws Exception if there are errors while searching the directory
501     */
502    private boolean hasAdministrativeDescendant( OperationContext opContext, Dn name ) throws LdapException
503    {
504        ExprNode filter = new PresenceNode( directoryService.getAtProvider().getAdministrativeRole() );
505        SearchControls controls = new SearchControls();
506        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
507
508        SearchOperationContext searchOperationContext = new SearchOperationContext( opContext.getSession(), name,
509            filter, controls );
510        searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
511        searchOperationContext.setTransaction( opContext.getTransaction() );
512        searchOperationContext.setPartition( opContext.getPartition() );
513
514        EntryFilteringCursor aps = nexus.search( searchOperationContext );
515
516        try
517        {
518            if ( aps.next() )
519            {
520                return true;
521            }
522        }
523        catch ( Exception e )
524        {
525            throw new LdapOperationException( e.getMessage(), e );
526        }
527        finally
528        {
529            try
530            {
531                aps.close();
532            }
533            catch ( Exception e )
534            {
535                LOG.error( I18n.err( I18n.ERR_168 ), e );
536            }
537        }
538
539        return false;
540    }
541
542
543    private List<Modification> getModsOnEntryRdnChange( Dn oldName, Dn newName, Entry entry ) throws LdapException
544    {
545        List<Modification> modifications = new ArrayList<>();
546
547        /*
548         * There are two different situations warranting action.  First if
549         * an ss evalutating to true with the old name no longer evalutates
550         * to true with the new name.  This would be caused by specific chop
551         * exclusions that effect the new name but did not effect the old
552         * name. In this case we must remove subentry operational attribute
553         * values associated with the dn of that subentry.
554         *
555         * In the second case an ss selects the entry with the new name when
556         * it did not previously with the old name. Again this situation
557         * would be caused by chop exclusions. In this case we must add subentry
558         * operational attribute values with the dn of this subentry.
559         */
560        SubentryCache subentryCache = directoryService.getSubentryCache();
561        SubtreeEvaluator evaluator = directoryService.getEvaluator();
562
563        for ( Dn subentryDn : subentryCache )
564        {
565            Dn apDn = subentryDn.getParent();
566            SubtreeSpecification ss = subentryCache.getSubentry( subentryDn ).getSubtreeSpecification();
567            boolean isOldNameSelected = evaluator.evaluate( ss, apDn, oldName, entry );
568            boolean isNewNameSelected = evaluator.evaluate( ss, apDn, newName, entry );
569
570            if ( isOldNameSelected == isNewNameSelected )
571            {
572                continue;
573            }
574
575            // need to remove references to the subentry
576            if ( isOldNameSelected && !isNewNameSelected )
577            {
578                for ( AttributeType operationalAttribute : directoryService.getAtProvider()
579                    .getSubentryOperationalAttributes() )
580                {
581                    ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE;
582                    Attribute opAttr = entry.get( operationalAttribute );
583
584                    if ( opAttr != null )
585                    {
586                        opAttr = opAttr.clone();
587                        opAttr.remove( subentryDn.getName() );
588
589                        if ( opAttr.size() < 1 )
590                        {
591                            op = ModificationOperation.REMOVE_ATTRIBUTE;
592                        }
593
594                        modifications.add( new DefaultModification( op, opAttr ) );
595                    }
596                }
597            }
598            // need to add references to the subentry
599            else if ( isNewNameSelected && !isOldNameSelected )
600            {
601                for ( AttributeType operationalAttribute : directoryService.getAtProvider()
602                    .getSubentryOperationalAttributes() )
603                {
604                    ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE;
605                    Attribute opAttr = new DefaultAttribute( operationalAttribute );
606                    opAttr.add( subentryDn.getName() );
607                    modifications.add( new DefaultModification( op, opAttr ) );
608                }
609            }
610        }
611
612        return modifications;
613    }
614
615
616    // -----------------------------------------------------------------------
617    // Methods dealing with subentry modification
618    // -----------------------------------------------------------------------
619
620    private Set<AdministrativeRole> getSubentryTypes( Entry entry, List<Modification> mods ) throws LdapException
621    {
622        Attribute ocFinalState = entry.get( directoryService.getAtProvider().getObjectClass() ).clone();
623
624        for ( Modification mod : mods )
625        {
626            if ( mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT )
627                || mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT_OID ) )
628            {
629                switch ( mod.getOperation() )
630                {
631                    case ADD_ATTRIBUTE:
632                        for ( Value value : mod.getAttribute() )
633                        {
634                            ocFinalState.add( value.getString() );
635                        }
636
637                        break;
638
639                    case REMOVE_ATTRIBUTE:
640                        for ( Value value : mod.getAttribute() )
641                        {
642                            ocFinalState.remove( value.getString() );
643                        }
644
645                        break;
646
647                    case REPLACE_ATTRIBUTE:
648                        ocFinalState = mod.getAttribute();
649                        break;
650
651                    default:
652                        throw new IllegalArgumentException( "Unexpected modify operatoin " + mod.getOperation() );
653                }
654            }
655        }
656
657        Entry attrs = new DefaultEntry( schemaManager, Dn.ROOT_DSE );
658        attrs.put( ocFinalState );
659        return getSubentryAdminRoles( attrs );
660    }
661
662
663    /**
664     * Update the list of modifications with a modification associated with a specific
665     * role, if it's requested.
666     */
667    private void getOperationalModForReplace( boolean hasRole, AttributeType attributeType, Entry entry, Dn oldDn,
668        Dn newDn, List<Modification> modifications ) throws LdapInvalidAttributeValueException
669    {
670        String oldDnStr = oldDn.getName();
671        String newDnStr = newDn.getName();
672
673        if ( hasRole )
674        {
675            Attribute operational = entry.get( attributeType ).clone();
676
677            if ( operational == null )
678            {
679                operational = new DefaultAttribute( attributeType, newDnStr );
680            }
681            else
682            {
683                operational.remove( oldDnStr );
684                operational.add( newDnStr );
685            }
686
687            modifications.add( new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) );
688        }
689    }
690
691
692    /**
693     * Get the list of modifications to be applied on an entry to inject the operational attributes
694     * associated with the administrative roles.
695     */
696    private List<Modification> getOperationalModsForReplace( Dn oldDn, Dn newDn, Subentry subentry, Entry entry )
697        throws LdapException
698    {
699        List<Modification> modifications = new ArrayList<>();
700
701        getOperationalModForReplace( subentry.isAccessControlAdminRole(), directoryService.getAtProvider()
702            .getAccessControlSubentries(), entry, oldDn, newDn, modifications );
703        getOperationalModForReplace( subentry.isSchemaAdminRole(), directoryService.getAtProvider()
704            .getSubschemaSubentry(), entry, oldDn, newDn, modifications );
705        getOperationalModForReplace( subentry.isCollectiveAdminRole(), directoryService.getAtProvider()
706            .getCollectiveAttributeSubentries(), entry, oldDn, newDn, modifications );
707        getOperationalModForReplace( subentry.isTriggersAdminRole(), directoryService.getAtProvider()
708            .getTriggerExecutionSubentries(), entry, oldDn, newDn, modifications );
709
710        return modifications;
711    }
712
713
714    /**
715     * Gets the subschema operational attributes to be added to or removed from
716     * an entry selected by a subentry's subtreeSpecification.
717     */
718    private List<Attribute> getSubentryOperationalAttributes( Dn dn, Subentry subentry ) throws LdapException
719    {
720        List<Attribute> attributes = new ArrayList<>();
721
722        if ( subentry.isAccessControlAdminRole() )
723        {
724            Attribute accessControlSubentries = new DefaultAttribute( directoryService.getAtProvider()
725                .getAccessControlSubentries(), dn.getName() );
726            attributes.add( accessControlSubentries );
727        }
728
729        if ( subentry.isSchemaAdminRole() )
730        {
731            Attribute subschemaSubentry = new DefaultAttribute(
732                directoryService.getAtProvider().getSubschemaSubentry(), dn.getName() );
733            attributes.add( subschemaSubentry );
734        }
735
736        if ( subentry.isCollectiveAdminRole() )
737        {
738            Attribute collectiveAttributeSubentries = new DefaultAttribute( directoryService.getAtProvider()
739                .getCollectiveAttributeSubentries(), dn.getName() );
740            attributes.add( collectiveAttributeSubentries );
741        }
742
743        if ( subentry.isTriggersAdminRole() )
744        {
745            Attribute tiggerExecutionSubentries = new DefaultAttribute( directoryService.getAtProvider()
746                .getTriggerExecutionSubentries(), dn.getName() );
747            attributes.add( tiggerExecutionSubentries );
748        }
749
750        return attributes;
751    }
752
753
754    /**
755     * Calculates the subentry operational attributes to remove from a candidate
756     * entry selected by a subtreeSpecification.  When we remove a subentry we
757     * must remove the operational attributes in the entries that were once selected
758     * by the subtree specification of that subentry.  To do so we must perform
759     * a modify operation with the set of modifications to perform.  This method
760     * calculates those modifications.
761     *
762     * @param subentryDn the distinguished name of the subentry
763     * @param candidate the candidate entry to removed from the
764     * @return the set of modifications required to remove an entry's reference to
765     * a subentry
766     */
767    private List<Modification> getOperationalModsForRemove( Dn subentryDn, Entry candidate ) throws LdapException
768    {
769        List<Modification> modifications = new ArrayList<>();
770        String dn = subentryDn.getName();
771
772        for ( AttributeType operationalAttribute : directoryService.getAtProvider().getSubentryOperationalAttributes() )
773        {
774            Attribute opAttr = candidate.get( operationalAttribute );
775
776            if ( ( opAttr != null ) && opAttr.contains( dn ) )
777            {
778                Attribute attr = new DefaultAttribute( operationalAttribute, dn );
779                modifications.add( new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, attr ) );
780            }
781        }
782
783        return modifications;
784    }
785
786
787    /**
788     * Calculates the subentry operational attributes to add or replace from
789     * a candidate entry selected by a subtree specification.  When a subentry
790     * is added or it's specification is modified some entries must have new
791     * operational attributes added to it to point back to the associated
792     * subentry.  To do so a modify operation must be performed on entries
793     * selected by the subtree specification.  This method calculates the
794     * modify operation to be performed on the entry.
795     */
796    private List<Modification> getOperationalModsForAdd( Entry entry, List<Attribute> operationalAttributes )
797        throws LdapException
798    {
799        List<Modification> modifications = new ArrayList<>();
800
801        for ( Attribute operationalAttribute : operationalAttributes )
802        {
803            Attribute opAttrInEntry = entry.get( operationalAttribute.getAttributeType() );
804
805            if ( ( opAttrInEntry != null ) && ( opAttrInEntry.size() > 0 ) )
806            {
807                Attribute newOperationalAttribute = operationalAttribute.clone();
808
809                for ( Value value : opAttrInEntry )
810                {
811                    newOperationalAttribute.add( value );
812                }
813
814                modifications.add( new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
815                    newOperationalAttribute ) );
816            }
817            else
818            {
819                modifications
820                    .add( new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, operationalAttribute ) );
821            }
822        }
823
824        return modifications;
825    }
826
827
828    /**
829     * Get the list of modification to apply to all the entries
830     */
831    private List<Modification> getModsOnEntryModification( Dn name, Entry oldEntry, Entry newEntry )
832        throws LdapException
833    {
834        List<Modification> modList = new ArrayList<>();
835
836        for ( Dn subentryDn : directoryService.getSubentryCache() )
837        {
838            Dn apDn = subentryDn.getParent();
839            SubtreeSpecification ss = directoryService.getSubentryCache().getSubentry( subentryDn )
840                .getSubtreeSpecification();
841            boolean isOldEntrySelected = directoryService.getEvaluator().evaluate( ss, apDn, name, oldEntry );
842            boolean isNewEntrySelected = directoryService.getEvaluator().evaluate( ss, apDn, name, newEntry );
843
844            if ( isOldEntrySelected == isNewEntrySelected )
845            {
846                continue;
847            }
848
849            // need to remove references to the subentry
850            if ( isOldEntrySelected && !isNewEntrySelected )
851            {
852                for ( AttributeType operationalAttribute : directoryService.getAtProvider()
853                    .getSubentryOperationalAttributes() )
854                {
855                    ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE;
856                    Attribute opAttr = oldEntry.get( operationalAttribute );
857
858                    if ( opAttr != null )
859                    {
860                        opAttr = opAttr.clone();
861                        opAttr.remove( subentryDn.getName() );
862
863                        if ( opAttr.size() < 1 )
864                        {
865                            op = ModificationOperation.REMOVE_ATTRIBUTE;
866                        }
867
868                        modList.add( new DefaultModification( op, opAttr ) );
869                    }
870                }
871            }
872            // need to add references to the subentry
873            else if ( isNewEntrySelected && !isOldEntrySelected )
874            {
875                for ( AttributeType operationalAttribute : directoryService.getAtProvider()
876                    .getSubentryOperationalAttributes() )
877                {
878                    ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE;
879                    Attribute opAttr = new DefaultAttribute( operationalAttribute );
880                    opAttr.add( subentryDn.getName() );
881                    modList.add( new DefaultModification( op, opAttr ) );
882                }
883            }
884        }
885
886        return modList;
887    }
888
889
890    /**
891     * Update the Operational Attribute with the reference to the subentry
892     */
893    private void setOperationalAttribute( Entry entry, Dn subentryDn, AttributeType opAttr ) throws LdapException
894    {
895        Attribute operational = entry.get( opAttr );
896
897        if ( operational == null )
898        {
899            operational = new DefaultAttribute( opAttr );
900            entry.put( operational );
901        }
902
903        operational.add( subentryDn.getName() );
904    }
905
906
907    //-------------------------------------------------------------------------------------------
908    // Interceptor API methods
909    //-------------------------------------------------------------------------------------------
910    /**
911     * {@inheritDoc}
912     */
913    @Override
914    public void add( AddOperationContext addContext ) throws LdapException
915    {
916        Dn dn = addContext.getDn();
917        Entry entry = addContext.getEntry();
918
919        // Check if the added entry is a subentry
920        if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) )
921        {
922            // get the name of the administrative point and its administrativeRole attributes
923            // The AP must be the parent Dn, but we also have to check that the given Dn
924            // is not the rootDSE or a NamingContext
925            if ( dn.isRootDse() || isNamingContext( dn ) )
926            {
927                // Not allowed : we can't get a parent in those cases
928                throw new LdapOtherException( "Cannot find an AdministrativePoint for " + dn );
929            }
930
931            // Get the administrativePoint role : we must have one immediately
932            // upper
933            Dn apDn = dn.getParent();
934            checkAdministrativeRole( addContext, apDn );
935
936            /* ----------------------------------------------------------------
937             * Build the set of operational attributes to be injected into
938             * entries that are contained within the subtree represented by this
939             * new subentry.  In the process we make sure the proper roles are
940             * supported by the administrative point to allow the addition of
941             * this new subentry.
942             * ----------------------------------------------------------------
943             */
944            Subentry subentry = new Subentry();
945            subentry.setAdministrativeRoles( getSubentryAdminRoles( entry ) );
946            List<Attribute> operationalAttributes = getSubentryOperationalAttributes( dn, subentry );
947
948            /* ----------------------------------------------------------------
949             * Parse the subtreeSpecification of the subentry and add it to the
950             * SubtreeSpecification cache.  If the parse succeeds we continue
951             * to add the entry to the DIT.  Thereafter we search out entries
952             * to modify the subentry operational attributes of.
953             * ----------------------------------------------------------------
954             */
955            setSubtreeSpecification( subentry, entry );
956            directoryService.getSubentryCache().addSubentry( dn, subentry );
957
958            // Now inject the subentry into the backend
959            next( addContext );
960
961            /* ----------------------------------------------------------------
962             * Find the baseDn for the subentry and use that to search the tree
963             * while testing each entry returned for inclusion within the
964             * subtree of the subentry's subtreeSpecification.  All included
965             * entries will have their operational attributes merged with the
966             * operational attributes calculated above.
967             * ----------------------------------------------------------------
968             */
969            Dn baseDn = apDn;
970            baseDn = baseDn.add( subentry.getSubtreeSpecification().getBase() );
971
972            updateEntries( addContext, OperationEnum.ADD, apDn, subentry.getSubtreeSpecification(),
973                baseDn, operationalAttributes );
974
975            // Store the newly modified entry into the context for later use in interceptor
976            // just in case
977            addContext.setEntry( entry );
978        }
979        else
980        {
981            // The added entry is not a Subentry.
982            // Nevertheless, we have to check if the entry is added into an AdministrativePoint
983            // and is associated with some SubtreeSpecification
984            // We brutally check *all* the subentries, as we don't hold a hierarchy
985            // of AP
986            // TODO : add a hierarchy of subentries
987            for ( Dn subentryDn : directoryService.getSubentryCache() )
988            {
989                Dn apDn = subentryDn.getParent();
990
991                // No need to evaluate the entry if it's not below an AP.
992                if ( dn.isDescendantOf( apDn ) )
993                {
994                    Subentry subentry = directoryService.getSubentryCache().getSubentry( subentryDn );
995                    SubtreeSpecification ss = subentry.getSubtreeSpecification();
996
997                    // Now, evaluate the entry wrt the subentry ss
998                    // and inject a ref to the subentry if it evaluates to true
999                    if ( directoryService.getEvaluator().evaluate( ss, apDn, dn, entry ) )
1000                    {
1001
1002                        if ( subentry.isAccessControlAdminRole() )
1003                        {
1004                            setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider()
1005                                .getAccessControlSubentries() );
1006                        }
1007
1008                        if ( subentry.isSchemaAdminRole() )
1009                        {
1010                            setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider()
1011                                .getSubschemaSubentry() );
1012                        }
1013
1014                        if ( subentry.isCollectiveAdminRole() )
1015                        {
1016                            setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider()
1017                                .getCollectiveAttributeSubentries() );
1018                        }
1019
1020                        if ( subentry.isTriggersAdminRole() )
1021                        {
1022                            setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider()
1023                                .getTriggerExecutionSubentries() );
1024                        }
1025                    }
1026                }
1027            }
1028
1029            // Now that the entry has been updated with the operational attributes,
1030            // we can update it into the add context
1031            addContext.setEntry( entry );
1032
1033            // Propagate the addition down to the backend.
1034            next( addContext );
1035        }
1036    }
1037
1038
1039    /**
1040     * {@inheritDoc}
1041     */
1042    @Override
1043    public void delete( DeleteOperationContext deleteContext ) throws LdapException
1044    {
1045        Dn dn = deleteContext.getDn();
1046        Entry entry = deleteContext.getEntry();
1047
1048        // If the entry has a "subentry" Objectclass, we can process the entry.
1049        // We first remove the re
1050        if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) )
1051        {
1052            Subentry removedSubentry = directoryService.getSubentryCache().getSubentry( dn );
1053
1054            /* ----------------------------------------------------------------
1055             * Find the baseDn for the subentry and use that to search the tree
1056             * for all entries included by the subtreeSpecification.  Then we
1057             * check the entry for subentry operational attribute that contain
1058             * the Dn of the subentry.  These are the subentry operational
1059             * attributes we remove from the entry in a modify operation.
1060             * ----------------------------------------------------------------
1061             */
1062            Dn apDn = dn.getParent();
1063            Dn baseDn = apDn;
1064            baseDn = baseDn.add( removedSubentry.getSubtreeSpecification().getBase() );
1065
1066            // Remove all the references to this removed subentry from all the selected entries
1067            updateEntries( deleteContext, OperationEnum.REMOVE, apDn,
1068                removedSubentry.getSubtreeSpecification(), baseDn, null );
1069
1070            // Update the cache
1071            directoryService.getSubentryCache().removeSubentry( dn );
1072
1073            // Now delete the subentry itself
1074            next( deleteContext );
1075        }
1076        else
1077        {
1078            // TODO : deal with AP removal.
1079            next( deleteContext );
1080        }
1081    }
1082
1083
1084    /**
1085     * {@inheritDoc}
1086     */
1087    @Override
1088    public void modify( ModifyOperationContext modifyContext ) throws LdapException
1089    {
1090        Dn dn = modifyContext.getDn();
1091        List<Modification> modifications = modifyContext.getModItems();
1092
1093        Entry entry = modifyContext.getEntry();
1094
1095        // We have three types of modifications :
1096        // 1) A modification applied on a normal entry
1097        // 2) A modification done on a subentry (the entry will have a 'subentry' ObjectClass)
1098        // 3) A modification on a normal entry on whch we add a 'subentry' ObjectClass
1099        // The third case is a transformation of a normal entry to a subentry. Not sure if it's
1100        // legal ...
1101
1102        boolean isSubtreeSpecificationModification = false;
1103        Modification subtreeMod = null;
1104
1105        // Find the subtreeSpecification
1106        for ( Modification mod : modifications )
1107        {
1108            if ( mod.getAttribute().getAttributeType()
1109                .equals( directoryService.getAtProvider().getSubtreeSpecification() ) )
1110            {
1111                isSubtreeSpecificationModification = true;
1112                subtreeMod = mod;
1113                break;
1114            }
1115        }
1116
1117        boolean containsSubentryOC = entry.contains( directoryService.getAtProvider().getObjectClass(),
1118            SchemaConstants.SUBENTRY_OC );
1119
1120        // Check if we have a modified subentry attribute in a Subentry entry
1121        if ( containsSubentryOC && isSubtreeSpecificationModification )
1122        {
1123            Subentry subentry = directoryService.getSubentryCache().removeSubentry( dn );
1124            SubtreeSpecification ssOld = subentry.getSubtreeSpecification();
1125            SubtreeSpecification ssNew;
1126
1127            try
1128            {
1129                ssNew = ssParser.parse( subtreeMod.getAttribute().getString() );
1130            }
1131            catch ( Exception e )
1132            {
1133                String msg = I18n.err( I18n.ERR_71 );
1134                LOG.error( msg, e );
1135                throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg );
1136            }
1137
1138            subentry.setSubtreeSpecification( ssNew );
1139            subentry.setAdministrativeRoles( getSubentryTypes( entry, modifications ) );
1140            directoryService.getSubentryCache().addSubentry( dn, subentry );
1141
1142            next( modifyContext );
1143
1144            // search for all entries selected by the old SS and remove references to subentry
1145            Dn apName = dn.getParent();
1146            Dn oldBaseDn = apName;
1147            oldBaseDn = oldBaseDn.add( ssOld.getBase() );
1148
1149            ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() );
1150            SearchControls controls = new SearchControls();
1151            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1152            controls.setReturningAttributes( new String[]
1153                { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
1154
1155            SearchOperationContext searchOperationContext = new SearchOperationContext( modifyContext.getSession(),
1156                oldBaseDn, filter, controls );
1157            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
1158            searchOperationContext.setPartition( modifyContext.getPartition() );
1159            searchOperationContext.setTransaction( modifyContext.getTransaction() );
1160
1161            EntryFilteringCursor subentries = nexus.search( searchOperationContext );
1162
1163            try
1164            {
1165                while ( subentries.next() )
1166                {
1167                    Entry candidate = subentries.get();
1168                    Dn candidateDn = candidate.getDn();
1169
1170                    if ( directoryService.getEvaluator().evaluate( ssOld, apName, candidateDn, candidate ) )
1171                    {
1172                        ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), candidateDn,
1173                            getOperationalModsForRemove( dn, candidate ) );
1174                        newModifyContext.setPartition( modifyContext.getPartition() );
1175                        newModifyContext.setTransaction( modifyContext.getTransaction() );
1176                        
1177                        nexus.modify( newModifyContext );
1178                    }
1179                }
1180
1181                subentries.close();
1182            }
1183            catch ( Exception e )
1184            {
1185                throw new LdapOperationErrorException( e.getMessage(), e );
1186            }
1187            finally
1188            {
1189                try
1190                {
1191                    subentries.close();
1192                }
1193                catch ( Exception e )
1194                {
1195                    LOG.error( I18n.err( I18n.ERR_168 ), e );
1196                }
1197            }
1198
1199            // search for all selected entries by the new SS and add references to subentry
1200            subentry = directoryService.getSubentryCache().getSubentry( dn );
1201            List<Attribute> operationalAttributes = getSubentryOperationalAttributes( dn, subentry );
1202            Dn newBaseDn = apName;
1203            newBaseDn = newBaseDn.add( ssNew.getBase() );
1204
1205            searchOperationContext = new SearchOperationContext( modifyContext.getSession(), newBaseDn, filter,
1206                controls );
1207            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
1208            searchOperationContext.setPartition( modifyContext.getPartition() );
1209            searchOperationContext.setTransaction( modifyContext.getTransaction() );
1210
1211            subentries = nexus.search( searchOperationContext );
1212
1213            try
1214            {
1215                while ( subentries.next() )
1216                {
1217                    Entry candidate = subentries.get();
1218                    Dn candidateDn = candidate.getDn();
1219
1220                    if ( directoryService.getEvaluator().evaluate( ssNew, apName, candidateDn, candidate ) )
1221                    {
1222                        nexus.modify( new ModifyOperationContext( modifyContext.getSession(), candidateDn,
1223                            getOperationalModsForAdd( candidate, operationalAttributes ) ) );
1224                    }
1225                }
1226                subentries.close();
1227            }
1228            catch ( Exception e )
1229            {
1230                throw new LdapOperationErrorException( e.getMessage(), e );
1231            }
1232            finally
1233            {
1234                try
1235                {
1236                    subentries.close();
1237                }
1238                catch ( Exception e )
1239                {
1240                    LOG.error( I18n.err( I18n.ERR_168 ), e );
1241                }
1242            }
1243        }
1244        else
1245        {
1246            next( modifyContext );
1247
1248            if ( !containsSubentryOC )
1249            {
1250                Entry newEntry = modifyContext.getAlteredEntry();
1251
1252                List<Modification> subentriesOpAttrMods = getModsOnEntryModification( dn, entry, newEntry );
1253
1254                if ( !subentriesOpAttrMods.isEmpty() )
1255                {
1256                    ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), dn, subentriesOpAttrMods );
1257                    newModifyContext.setPartition( modifyContext.getPartition() );
1258                    newModifyContext.setTransaction( modifyContext.getTransaction() );
1259                    nexus.modify( newModifyContext );
1260                }
1261            }
1262        }
1263    }
1264
1265
1266    /**
1267     * The Move operation for a Subentry will deal with different cases :
1268     * 1) we move a normal entry
1269     * 2) we move a subentry
1270     * 3) we move an administrationPoint
1271     * <p>
1272     * <u>Case 1 :</u><br>
1273     * A normal entry (ie, not a subentry or an AP) may be part of some administrative areas.
1274     * We have to remove the references to the associated areas if the entry gets out of them.<br>
1275     * This entry can also be moved to some other administrative area, and it should then be
1276     * updated to point to the associated subentries.
1277     * <br><br>
1278     * There is one preliminary condition : If the entry has a descendant which is an
1279     * Administrative Point, then the move cannot be done.
1280     * <br><br>
1281     * <u>Case 2 :</u><br>
1282     * The subentry has to be moved under a new AP, otherwise this is an error. Once moved,
1283     * we have to update all the entries selected by the old subtreeSpecification, removing
1284     * the references to the subentry from all the selected entry, and update the entries
1285     * selected by the new subtreeSpecification by adding a reference to the subentry into them.
1286     * <br><br>
1287     * <u>Case 3 :</u><br>
1288     *
1289     *
1290     * @param moveContext The context containing all the needed informations to proceed
1291     * @throws LdapException If the move failed
1292     */
1293    @Override
1294    public void move( MoveOperationContext moveContext ) throws LdapException
1295    {
1296        Dn oldDn = moveContext.getDn();
1297        Dn newSuperiorDn = moveContext.getNewSuperior();
1298
1299        Entry entry = moveContext.getOriginalEntry();
1300
1301        if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) )
1302        {
1303            // This is a subentry. Moving a subentry means we have to:
1304            // o Check that there is a new AP where we move the subentry
1305            // o Remove the op Attr from all the entry selected by the subentry
1306            // o Add the op Attr in all the selected entry by the subentry
1307
1308            // If we move it, we have to check that
1309            // the new parent is an AP
1310            checkAdministrativeRole( moveContext, newSuperiorDn );
1311
1312            Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn );
1313            SubtreeSpecification ss = subentry.getSubtreeSpecification();
1314            Dn apName = oldDn.getParent();
1315            Dn baseDn = apName;
1316            baseDn = baseDn.add( ss.getBase() );
1317            Dn newName = newSuperiorDn;
1318            newName = newName.add( oldDn.getRdn() );
1319            
1320            if ( !newName.isSchemaAware() )
1321            {
1322                newName = new Dn( schemaManager, newName );
1323            }
1324
1325            directoryService.getSubentryCache().addSubentry( newName, subentry );
1326
1327            next( moveContext );
1328
1329            subentry = directoryService.getSubentryCache().getSubentry( newName );
1330
1331            ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() );
1332            SearchControls controls = new SearchControls();
1333            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1334            controls.setReturningAttributes( new String[]
1335                { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
1336
1337            SearchOperationContext searchOperationContext = new SearchOperationContext( moveContext.getSession(),
1338                baseDn,
1339                filter, controls );
1340            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
1341            searchOperationContext.setPartition( moveContext.getPartition() );
1342            searchOperationContext.setTransaction( moveContext.getTransaction() );
1343
1344            EntryFilteringCursor subentries = nexus.search( searchOperationContext );
1345
1346            try
1347            {
1348                // Modify all the entries under this subentry
1349                while ( subentries.next() )
1350                {
1351                    Entry candidate = subentries.get();
1352                    Dn dn = candidate.getDn();
1353                    
1354                    if ( !dn.isSchemaAware() )
1355                    {
1356                        dn = new Dn( schemaManager, dn );
1357                    }
1358
1359                    if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) )
1360                    {
1361                        ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), dn,
1362                            getOperationalModsForReplace( oldDn, newName, subentry, candidate ) );
1363                        newModifyContext.setPartition( moveContext.getPartition() );
1364                        newModifyContext.setTransaction( moveContext.getTransaction() );
1365                        nexus.modify( newModifyContext );
1366                    }
1367                }
1368            }
1369            catch ( Exception e )
1370            {
1371                throw new LdapOperationException( e.getMessage(), e );
1372            }
1373            finally
1374            {
1375                try
1376                {
1377                    subentries.close();
1378                }
1379                catch ( Exception e )
1380                {
1381                    LOG.error( I18n.err( I18n.ERR_168 ), e );
1382                }
1383            }
1384        }
1385        else
1386        {
1387            // A normal entry. It may be part of a SubtreeSpecifciation. In this
1388            // case, we have to update the opAttrs (removing old ones and adding the
1389            // new ones)
1390
1391            // First, an moved entry which has an AP in one of its descendant
1392            // can't be moved.
1393            if ( hasAdministrativeDescendant( moveContext, oldDn ) )
1394            {
1395                String msg = I18n.err( I18n.ERR_308 );
1396                LOG.warn( msg );
1397                throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg );
1398            }
1399
1400            // Move the entry
1401            next( moveContext );
1402
1403            // calculate the new Dn now for use below to modify subentry operational
1404            // attributes contained within this regular entry with name changes
1405            Dn newDn = moveContext.getNewDn();
1406            List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry );
1407
1408            // Update the entry operational attributes
1409            if ( !mods.isEmpty() )
1410            {
1411                ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), newDn, mods );
1412                newModifyContext.setPartition( moveContext.getPartition() );
1413                newModifyContext.setTransaction( moveContext.getTransaction() );
1414                nexus.modify( newModifyContext );
1415            }
1416        }
1417    }
1418
1419
1420    /**
1421     * {@inheritDoc}
1422     */
1423    @Override
1424    public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
1425    {
1426        Dn oldDn = moveAndRenameContext.getDn();
1427        Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
1428
1429        Entry entry = moveAndRenameContext.getOriginalEntry();
1430
1431        if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) )
1432        {
1433            Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn );
1434            SubtreeSpecification ss = subentry.getSubtreeSpecification();
1435            Dn apName = oldDn.getParent();
1436            Dn baseDn = apName;
1437            baseDn = baseDn.add( ss.getBase() );
1438            Dn newName = newSuperiorDn.getParent();
1439
1440            newName = newName.add( moveAndRenameContext.getNewRdn() );
1441            
1442            if ( !newName.isSchemaAware() )
1443            {
1444                newName = new Dn( schemaManager, newName );
1445            }
1446
1447            directoryService.getSubentryCache().addSubentry( newName, subentry );
1448
1449            next( moveAndRenameContext );
1450
1451            subentry = directoryService.getSubentryCache().getSubentry( newName );
1452
1453            ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() );
1454            SearchControls controls = new SearchControls();
1455            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1456            controls.setReturningAttributes( new String[]
1457                { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
1458
1459            SearchOperationContext searchOperationContext = new SearchOperationContext(
1460                moveAndRenameContext.getSession(), baseDn,
1461                filter, controls );
1462            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
1463            searchOperationContext.setPartition( moveAndRenameContext.getPartition() );
1464            searchOperationContext.setTransaction( moveAndRenameContext.getTransaction() );
1465
1466            EntryFilteringCursor subentries = nexus.search( searchOperationContext );
1467
1468            try
1469            {
1470                while ( subentries.next() )
1471                {
1472                    Entry candidate = subentries.get();
1473                    Dn dn = candidate.getDn();
1474                    
1475                    if ( !dn.isSchemaAware() )
1476                    {
1477                        dn = new Dn( schemaManager, dn );
1478                    }
1479
1480                    if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) )
1481                    {
1482                        ModifyOperationContext newModifyContext = new ModifyOperationContext( moveAndRenameContext.getSession(), dn,
1483                            getOperationalModsForReplace( oldDn, newName, subentry, candidate ) );
1484                        newModifyContext.setPartition( moveAndRenameContext.getPartition() );
1485                        newModifyContext.setTransaction( moveAndRenameContext.getTransaction() );
1486                        nexus.modify( newModifyContext );
1487                    }
1488                }
1489            }
1490            catch ( Exception e )
1491            {
1492                throw new LdapOperationException( e.getMessage(), e );
1493            }
1494            finally
1495            {
1496                try
1497                {
1498                    subentries.close();
1499                }
1500                catch ( Exception e )
1501                {
1502                    LOG.error( I18n.err( I18n.ERR_168 ), e );
1503                }
1504            }
1505        }
1506        else
1507        {
1508            if ( hasAdministrativeDescendant( moveAndRenameContext, oldDn ) )
1509            {
1510                String msg = I18n.err( I18n.ERR_308 );
1511                LOG.warn( msg );
1512                throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg );
1513            }
1514
1515            next( moveAndRenameContext );
1516
1517            // calculate the new Dn now for use below to modify subentry operational
1518            // attributes contained within this regular entry with name changes
1519            Dn newDn = moveAndRenameContext.getNewDn();
1520            List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry );
1521
1522            if ( !mods.isEmpty() )
1523            {
1524                nexus.modify( new ModifyOperationContext( moveAndRenameContext.getSession(), newDn, mods ) );
1525            }
1526        }
1527    }
1528
1529
1530    /**
1531     * {@inheritDoc}
1532     */
1533    @Override
1534    public void rename( RenameOperationContext renameContext ) throws LdapException
1535    {
1536        Dn oldDn = renameContext.getDn();
1537
1538        Entry entry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getClonedEntry();
1539
1540        if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) )
1541        {
1542            // @Todo To be reviewed !!!
1543            Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn );
1544            SubtreeSpecification ss = subentry.getSubtreeSpecification();
1545            Dn apName = oldDn.getParent();
1546            Dn baseDn = apName;
1547            baseDn = baseDn.add( ss.getBase() );
1548            Dn newName = oldDn.getParent();
1549
1550            newName = newName.add( renameContext.getNewRdn() );
1551
1552            if ( !newName.isSchemaAware() )
1553            {
1554                newName = new Dn( schemaManager, newName );
1555            }
1556
1557            directoryService.getSubentryCache().addSubentry( newName, subentry );
1558            next( renameContext );
1559
1560            subentry = directoryService.getSubentryCache().getSubentry( newName );
1561            ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() );
1562            SearchControls controls = new SearchControls();
1563            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1564            controls.setReturningAttributes( new String[]
1565                { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
1566
1567            SearchOperationContext searchOperationContext = new SearchOperationContext( renameContext.getSession(),
1568                baseDn,
1569                filter, controls );
1570            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
1571            searchOperationContext.setPartition( renameContext.getPartition() );
1572            searchOperationContext.setTransaction( renameContext.getTransaction() );
1573
1574            EntryFilteringCursor subentries = nexus.search( searchOperationContext );
1575
1576            try
1577            {
1578                while ( subentries.next() )
1579                {
1580                    Entry candidate = subentries.get();
1581                    Dn dn = candidate.getDn();
1582
1583                    if ( !dn.isSchemaAware() )
1584                    {
1585                        dn = new Dn( schemaManager, dn );
1586                    }
1587
1588                    if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) )
1589                    {
1590                        nexus.modify( new ModifyOperationContext( renameContext.getSession(), dn,
1591                            getOperationalModsForReplace(
1592                                oldDn, newName, subentry, candidate ) ) );
1593                    }
1594                }
1595            }
1596            catch ( Exception e )
1597            {
1598                throw new LdapOperationException( e.getMessage(), e );
1599            }
1600            finally
1601            {
1602                try
1603                {
1604                    subentries.close();
1605                }
1606                catch ( Exception e )
1607                {
1608                    LOG.error( I18n.err( I18n.ERR_168 ), e );
1609                }
1610            }
1611        }
1612        else
1613        {
1614            if ( hasAdministrativeDescendant( renameContext, oldDn ) )
1615            {
1616                String msg = I18n.err( I18n.ERR_308 );
1617                LOG.warn( msg );
1618                throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg );
1619            }
1620
1621            next( renameContext );
1622
1623            // calculate the new Dn now for use below to modify subentry operational
1624            // attributes contained within this regular entry with name changes
1625            Dn newName = renameContext.getNewDn();
1626
1627            List<Modification> mods = getModsOnEntryRdnChange( oldDn, newName, entry );
1628
1629            if ( !mods.isEmpty() )
1630            {
1631                ModifyOperationContext newModifyContext = new ModifyOperationContext( renameContext.getSession(), newName, mods );
1632                newModifyContext.setPartition( renameContext.getPartition() );
1633                newModifyContext.setTransaction( renameContext.getTransaction() );
1634                nexus.modify( newModifyContext );
1635            }
1636        }
1637    }
1638
1639
1640    /**
1641     * {@inheritDoc}
1642     */
1643    @Override
1644    public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
1645    {
1646        EntryFilteringCursor cursor = next( searchContext );
1647
1648        // object scope searches by default return subentries
1649        if ( searchContext.getScope() == SearchScope.OBJECT )
1650        {
1651            return cursor;
1652        }
1653
1654        // DO NOT hide subentries for replication operations
1655        if ( searchContext.isSyncreplSearch() )
1656        {
1657            return cursor;
1658        }
1659
1660        // for subtree and one level scope we filter
1661        if ( !isSubentryVisible( searchContext ) )
1662        {
1663            cursor.addEntryFilter( new HideSubentriesFilter() );
1664        }
1665        else
1666        {
1667            cursor.addEntryFilter( new HideEntriesFilter() );
1668        }
1669
1670        return cursor;
1671    }
1672}