View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.subtree;
21  
22  
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Set;
27  
28  import javax.naming.directory.SearchControls;
29  
30  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
31  import org.apache.directory.api.ldap.model.entry.Attribute;
32  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
33  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
34  import org.apache.directory.api.ldap.model.entry.DefaultModification;
35  import org.apache.directory.api.ldap.model.entry.Entry;
36  import org.apache.directory.api.ldap.model.entry.Modification;
37  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
38  import org.apache.directory.api.ldap.model.entry.Value;
39  import org.apache.directory.api.ldap.model.exception.LdapException;
40  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
41  import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
42  import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
43  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
44  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
45  import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
46  import org.apache.directory.api.ldap.model.filter.EqualityNode;
47  import org.apache.directory.api.ldap.model.filter.ExprNode;
48  import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
49  import org.apache.directory.api.ldap.model.filter.PresenceNode;
50  import org.apache.directory.api.ldap.model.message.AliasDerefMode;
51  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
52  import org.apache.directory.api.ldap.model.message.SearchScope;
53  import org.apache.directory.api.ldap.model.message.controls.Subentries;
54  import org.apache.directory.api.ldap.model.name.Dn;
55  import org.apache.directory.api.ldap.model.schema.AttributeType;
56  import org.apache.directory.api.ldap.model.subtree.AdministrativeRole;
57  import org.apache.directory.api.ldap.model.subtree.Subentry;
58  import org.apache.directory.api.ldap.model.subtree.SubtreeSpecification;
59  import org.apache.directory.api.ldap.model.subtree.SubtreeSpecificationParser;
60  import org.apache.directory.server.constants.ApacheSchemaConstants;
61  import org.apache.directory.server.core.api.CoreSession;
62  import org.apache.directory.server.core.api.DirectoryService;
63  import org.apache.directory.server.core.api.InterceptorEnum;
64  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
65  import org.apache.directory.server.core.api.filtering.EntryFilter;
66  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
67  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
68  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
69  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
70  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
71  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
72  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
73  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
74  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
75  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
76  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
77  import org.apache.directory.server.core.api.partition.Partition;
78  import org.apache.directory.server.core.api.partition.PartitionNexus;
79  import org.apache.directory.server.core.api.subtree.SubentryCache;
80  import org.apache.directory.server.core.api.subtree.SubtreeEvaluator;
81  import org.apache.directory.server.i18n.I18n;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  
86  /**
87   * The Subentry interceptor service which is responsible for filtering
88   * out subentries on search operations and injecting operational attributes
89   *
90   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
91   */
92  public class SubentryInterceptor extends BaseInterceptor
93  {
94      /** The logger for this class */
95      private static final Logger LOG = LoggerFactory.getLogger( SubentryInterceptor.class );
96  
97      /** the subentry control OID */
98      private static final String SUBENTRY_CONTROL = Subentries.OID;
99  
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             SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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         SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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                     ModifyOperationContexttor/context/ModifyOperationContext.html#ModifyOperationContext">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         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">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         SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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             SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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             SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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             SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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             SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">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/context/ModifyOperationContext.html#ModifyOperationContext">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 }