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 org.apache.directory.server.core.interceptor.BaseInterceptor;
24  
25  import org.apache.directory.server.constants.ApacheSchemaConstants;
26  import org.apache.directory.server.constants.ServerDNConstants;
27  import org.apache.directory.server.core.CoreSession;
28  import org.apache.directory.server.core.DefaultCoreSession;
29  import org.apache.directory.server.core.DirectoryService;
30  import org.apache.directory.server.core.authn.LdapPrincipal;
31  import org.apache.directory.server.core.entry.ClonedServerEntry;
32  import org.apache.directory.server.core.entry.DefaultServerAttribute;
33  import org.apache.directory.server.core.entry.DefaultServerEntry;
34  import org.apache.directory.server.core.entry.ServerAttribute;
35  import org.apache.directory.server.core.entry.ServerEntry;
36  import org.apache.directory.server.core.entry.ServerModification;
37  import org.apache.directory.server.core.filtering.EntryFilter;
38  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
39  import org.apache.directory.server.core.interceptor.NextInterceptor;
40  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
41  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
42  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
43  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
44  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
45  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
46  import org.apache.directory.server.core.interceptor.context.OperationContext;
47  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
48  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
49  import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
50  import org.apache.directory.server.core.partition.ByPassConstants;
51  import org.apache.directory.server.core.partition.PartitionNexus;
52  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
53  import org.apache.directory.server.schema.registries.OidRegistry;
54  import org.apache.directory.server.schema.registries.Registries;
55  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
56  import org.apache.directory.shared.ldap.constants.SchemaConstants;
57  import org.apache.directory.shared.ldap.entry.EntryAttribute;
58  import org.apache.directory.shared.ldap.entry.Modification;
59  import org.apache.directory.shared.ldap.entry.ModificationOperation;
60  import org.apache.directory.shared.ldap.entry.Value;
61  import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
62  import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
63  import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
64  import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
65  import org.apache.directory.shared.ldap.filter.EqualityNode;
66  import org.apache.directory.shared.ldap.filter.ExprNode;
67  import org.apache.directory.shared.ldap.filter.PresenceNode;
68  import org.apache.directory.shared.ldap.filter.SearchScope;
69  import org.apache.directory.shared.ldap.message.AliasDerefMode;
70  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
71  import org.apache.directory.shared.ldap.message.SubentriesControl;
72  import org.apache.directory.shared.ldap.name.LdapDN;
73  import org.apache.directory.shared.ldap.schema.AttributeType;
74  import org.apache.directory.shared.ldap.schema.NormalizerMappingResolver;
75  import org.apache.directory.shared.ldap.schema.OidNormalizer;
76  import org.apache.directory.shared.ldap.subtree.SubtreeSpecification;
77  import org.apache.directory.shared.ldap.subtree.SubtreeSpecificationParser;
78  import org.slf4j.Logger;
79  import org.slf4j.LoggerFactory;
80  
81  import javax.naming.Name;
82  import javax.naming.directory.SearchControls;
83  import java.util.ArrayList;
84  import java.util.Iterator;
85  import java.util.List;
86  import java.util.Map;
87  
88  
89  /**
90   * The Subentry interceptor service which is responsible for filtering
91   * out subentries on search operations and injecting operational attributes
92   *
93   * @org.apache.xbean.XBean
94   *
95   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
96   * @version $Rev: 676207 $
97   */
98  public class SubentryInterceptor extends BaseInterceptor
99  {
100     /** the subentry control OID */
101     private static final String SUBENTRY_CONTROL = SubentriesControl.CONTROL_OID;
102 
103     public static final String AC_AREA = "accessControlSpecificArea";
104     public static final String AC_INNERAREA = "accessControlInnerArea";
105 
106     public static final String SCHEMA_AREA = "subschemaAdminSpecificArea";
107 
108     public static final String COLLECTIVE_AREA = "collectiveAttributeSpecificArea";
109     public static final String COLLECTIVE_INNERAREA = "collectiveAttributeInnerArea";
110 
111     public static final String TRIGGER_AREA = "triggerExecutionSpecificArea";
112     public static final String TRIGGER_INNERAREA = "triggerExecutionInnerArea";
113 
114     public static final String[] SUBENTRY_OPATTRS =
115         { SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT, SchemaConstants.SUBSCHEMA_SUBENTRY_AT,
116             SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT, SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT };
117 
118     private static final Logger LOG = LoggerFactory.getLogger( SubentryInterceptor.class );
119 
120     /** the hash mapping the DN of a subentry to its SubtreeSpecification/types */
121     private final SubentryCache subentryCache = new SubentryCache();
122 
123     private SubtreeSpecificationParser ssParser;
124     private SubtreeEvaluator evaluator;
125     private PartitionNexus nexus;
126 
127     /** The global registries */
128     private Registries registries;
129 
130     /** The AttributeType registry */
131     private AttributeTypeRegistry atRegistry;
132 
133     /** The OID registry */
134     private OidRegistry oidRegistry;
135 
136     private AttributeType objectClassType;
137 
138 
139     public void init( DirectoryService directoryService ) throws Exception
140     {
141         super.init( directoryService );
142         nexus = directoryService.getPartitionNexus();
143         registries = directoryService.getRegistries();
144         atRegistry = registries.getAttributeTypeRegistry();
145         oidRegistry = registries.getOidRegistry();
146 
147         // setup various attribute type values
148         objectClassType = atRegistry.lookup( oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT ) );
149 
150         ssParser = new SubtreeSpecificationParser( new NormalizerMappingResolver()
151         {
152             public Map<String, OidNormalizer> getNormalizerMapping() throws Exception
153             {
154                 return atRegistry.getNormalizerMapping();
155             }
156         }, atRegistry.getNormalizerMapping() );
157         evaluator = new SubtreeEvaluator( oidRegistry, atRegistry );
158 
159         // prepare to find all subentries in all namingContexts
160         Iterator<String> suffixes = this.nexus.listSuffixes( null );
161         ExprNode filter = new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, new ClientStringValue(
162             SchemaConstants.SUBENTRY_OC ) );
163         SearchControls controls = new SearchControls();
164         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
165         controls.setReturningAttributes( new String[]
166             { SchemaConstants.SUBTREE_SPECIFICATION_AT, SchemaConstants.OBJECT_CLASS_AT } );
167 
168         // search each namingContext for subentries
169         while ( suffixes.hasNext() )
170         {
171             LdapDN suffix = new LdapDN( suffixes.next() );
172             //suffix = LdapDN.normalize( suffix, registry.getNormalizerMapping() );
173             suffix.normalize( atRegistry.getNormalizerMapping() );
174 
175             LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
176             adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
177             CoreSession adminSession = new DefaultCoreSession( 
178                 new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
179 
180             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( adminSession,
181                 suffix, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
182 
183             while ( subentries.next() )
184             {
185                 ServerEntry subentry = subentries.get();
186                 LdapDN dnName = subentry.getDn();
187 
188                 String subtree = subentry.get( SchemaConstants.SUBTREE_SPECIFICATION_AT ).getString();
189                 SubtreeSpecification ss;
190 
191                 try
192                 {
193                     ss = ssParser.parse( subtree );
194                 }
195                 catch ( Exception e )
196                 {
197                     LOG.warn( "Failed while parsing subtreeSpecification for " + dnName );
198                     continue;
199                 }
200 
201                 dnName.normalize( atRegistry.getNormalizerMapping() );
202                 subentryCache.setSubentry( dnName.toString(), ss, getSubentryTypes( subentry ) );
203             }
204         }
205     }
206 
207 
208     private int getSubentryTypes( ServerEntry subentry ) throws Exception
209     {
210         int types = 0;
211 
212         EntryAttribute oc = subentry.get( SchemaConstants.OBJECT_CLASS_AT );
213 
214         if ( oc == null )
215         {
216             throw new LdapSchemaViolationException( "A subentry must have an objectClass attribute",
217                 ResultCodeEnum.OBJECT_CLASS_VIOLATION );
218         }
219 
220         if ( oc.contains( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) )
221         {
222             types |= Subentry.ACCESS_CONTROL_SUBENTRY;
223         }
224 
225         if ( oc.contains( "subschema" ) )
226         {
227             types |= Subentry.SCHEMA_SUBENTRY;
228         }
229 
230         if ( oc.contains( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) )
231         {
232             types |= Subentry.COLLECTIVE_SUBENTRY;
233         }
234 
235         if ( oc.contains( ApacheSchemaConstants.TRIGGER_EXECUTION_SUBENTRY_OC ) )
236         {
237             types |= Subentry.TRIGGER_SUBENTRY;
238         }
239 
240         return types;
241     }
242 
243 
244     // -----------------------------------------------------------------------
245     // Methods/Code dealing with Subentry Visibility
246     // -----------------------------------------------------------------------
247 
248     
249     public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext )
250         throws Exception
251     {
252         EntryFilteringCursor cursor = nextInterceptor.list( opContext );
253 
254         if ( !isSubentryVisible( opContext ) )
255         {
256             cursor.addEntryFilter( new HideSubentriesFilter() );
257         }
258 
259         return cursor;
260     }
261 
262 
263     public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) 
264         throws Exception
265     {
266         EntryFilteringCursor cursor = nextInterceptor.search( opContext );
267 
268         // object scope searches by default return subentries
269         if ( opContext.getScope() == SearchScope.OBJECT )
270         {
271             return cursor;
272         }
273 
274         // for subtree and one level scope we filter
275         if ( !isSubentryVisible( opContext ) )
276         {
277             cursor.addEntryFilter( new HideSubentriesFilter() );
278         }
279         else
280         {
281             cursor.addEntryFilter( new HideEntriesFilter() );
282         }
283 
284         return cursor;
285     }
286 
287 
288     /**
289      * Checks to see if subentries for the search and list operations should be
290      * made visible based on the availability of the search request control
291      *
292      * @param invocation the invocation object to use for determining subentry visibility
293      * @return true if subentries should be visible, false otherwise
294      * @throws Exception if there are problems accessing request controls
295      */
296     private boolean isSubentryVisible( OperationContext opContext ) throws Exception
297     {
298         if ( !opContext.hasRequestControls() )
299         {
300             return false;
301         }
302 
303         // found the subentry request control so we return its value
304         if ( opContext.hasRequestControl( SUBENTRY_CONTROL ) )
305         {
306             SubentriesControl subentriesControl = ( SubentriesControl ) opContext.getRequestControl( SUBENTRY_CONTROL );
307             return subentriesControl.isVisible();
308         }
309 
310         return false;
311     }
312 
313 
314     // -----------------------------------------------------------------------
315     // Methods dealing with entry and subentry addition
316     // -----------------------------------------------------------------------
317 
318     /**
319      * Evaluates the set of subentry subtrees upon an entry and returns the
320      * operational subentry attributes that will be added to the entry if
321      * added at the dn specified.
322      *
323      * @param dn the normalized distinguished name of the entry
324      * @param entryAttrs the entry attributes are generated for
325      * @return the set of subentry op attrs for an entry
326      * @throws Exception if there are problems accessing entry information
327      */
328     public ServerEntry getSubentryAttributes( LdapDN dn, ServerEntry entryAttrs ) throws Exception
329     {
330         ServerEntry subentryAttrs = new DefaultServerEntry( registries, dn );
331         Iterator<String> list = subentryCache.nameIterator();
332 
333         while ( list.hasNext() )
334         {
335             String subentryDnStr = list.next();
336             LdapDN subentryDn = new LdapDN( subentryDnStr );
337             LdapDN apDn = ( LdapDN ) subentryDn.clone();
338             apDn.remove( apDn.size() - 1 );
339             Subentry subentry = subentryCache.getSubentry( subentryDnStr );
340             SubtreeSpecification ss = subentry.getSubtreeSpecification();
341 
342             if ( evaluator.evaluate( ss, apDn, dn, entryAttrs ) )
343             {
344                 EntryAttribute operational;
345 
346                 if ( subentry.isAccessControlSubentry() )
347                 {
348                     operational = subentryAttrs.get( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT );
349 
350                     if ( operational == null )
351                     {
352                         operational = new DefaultServerAttribute( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT,
353                             atRegistry.lookup( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ) );
354                         subentryAttrs.put( operational );
355                     }
356 
357                     operational.add( subentryDn.toString() );
358                 }
359                 if ( subentry.isSchemaSubentry() )
360                 {
361                     operational = subentryAttrs.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT );
362 
363                     if ( operational == null )
364                     {
365                         operational = new DefaultServerAttribute( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, atRegistry
366                             .lookup( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ) );
367                         subentryAttrs.put( operational );
368                     }
369 
370                     operational.add( subentryDn.toString() );
371                 }
372                 if ( subentry.isCollectiveSubentry() )
373                 {
374                     operational = subentryAttrs.get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT );
375 
376                     if ( operational == null )
377                     {
378                         operational = new DefaultServerAttribute( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT,
379                             atRegistry.lookup( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ) );
380                         subentryAttrs.put( operational );
381                     }
382 
383                     operational.add( subentryDn.toString() );
384                 }
385                 if ( subentry.isTriggerSubentry() )
386                 {
387                     operational = subentryAttrs.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT );
388 
389                     if ( operational == null )
390                     {
391                         operational = new DefaultServerAttribute( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT,
392                             atRegistry.lookup( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ) );
393                         subentryAttrs.put( operational );
394                     }
395 
396                     operational.add( subentryDn.toString() );
397                 }
398             }
399         }
400 
401         return subentryAttrs;
402     }
403 
404 
405     public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
406     {
407         LdapDN name = addContext.getDn();
408         ClonedServerEntry entry = addContext.getEntry();
409 
410         EntryAttribute objectClasses = entry.get( SchemaConstants.OBJECT_CLASS_AT );
411 
412         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
413         {
414             // get the name of the administrative point and its administrativeRole attributes
415             LdapDN apName = ( LdapDN ) name.clone();
416             apName.remove( name.size() - 1 );
417             ServerEntry ap = addContext.lookup( apName, ByPassConstants.LOOKUP_BYPASS );
418             EntryAttribute administrativeRole = ap.get( "administrativeRole" );
419 
420             // check that administrativeRole has something valid in it for us
421             if ( administrativeRole == null || administrativeRole.size() <= 0 )
422             {
423                 throw new LdapNoSuchAttributeException( "Administration point " + apName
424                     + " does not contain an administrativeRole attribute! An"
425                     + " administrativeRole attribute in the administrative point is"
426                     + " required to add a subordinate subentry." );
427             }
428 
429             /* ----------------------------------------------------------------
430              * Build the set of operational attributes to be injected into
431              * entries that are contained within the subtree repesented by this
432              * new subentry.  In the process we make sure the proper roles are
433              * supported by the administrative point to allow the addition of
434              * this new subentry.
435              * ----------------------------------------------------------------
436              */
437             Subentry subentry = new Subentry();
438             subentry.setTypes( getSubentryTypes( entry ) );
439             ServerEntry operational = getSubentryOperatationalAttributes( name, subentry );
440 
441             /* ----------------------------------------------------------------
442              * Parse the subtreeSpecification of the subentry and add it to the
443              * SubtreeSpecification cache.  If the parse succeeds we continue
444              * to add the entry to the DIT.  Thereafter we search out entries
445              * to modify the subentry operational attributes of.
446              * ----------------------------------------------------------------
447              */
448             String subtree = entry.get( SchemaConstants.SUBTREE_SPECIFICATION_AT ).getString();
449             SubtreeSpecification ss;
450 
451             try
452             {
453                 ss = ssParser.parse( subtree );
454             }
455             catch ( Exception e )
456             {
457                 String msg = "Failed while parsing subtreeSpecification for " + name.getUpName();
458                 LOG.warn( msg );
459                 throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX );
460             }
461 
462             subentryCache.setSubentry( name.getNormName(), ss, getSubentryTypes( entry ) );
463 
464             next.add( addContext );
465 
466             /* ----------------------------------------------------------------
467              * Find the baseDn for the subentry and use that to search the tree
468              * while testing each entry returned for inclusion within the
469              * subtree of the subentry's subtreeSpecification.  All included
470              * entries will have their operational attributes merged with the
471              * operational attributes calculated above.
472              * ----------------------------------------------------------------
473              */
474             LdapDN baseDn = ( LdapDN ) apName.clone();
475             baseDn.addAll( ss.getBase() );
476 
477             ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT_OID ); // (objectClass=*)
478             SearchControls controls = new SearchControls();
479             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
480             controls.setReturningAttributes( new String[]
481                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
482 
483             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( addContext.getSession(),
484                 baseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
485 
486             while ( subentries.next() )
487             {
488                 ServerEntry candidate = subentries.get();
489                 LdapDN dn = candidate.getDn();
490                 dn.normalize( atRegistry.getNormalizerMapping() );
491 
492                 if ( evaluator.evaluate( ss, apName, dn, candidate ) )
493                 {
494                     nexus.modify( new ModifyOperationContext( addContext.getSession(), dn, 
495                         getOperationalModsForAdd( candidate, operational ) ) );
496                 }
497             }
498 
499             // TODO why are we doing this here if we got the entry from the 
500             // opContext in the first place - got to look into this 
501             addContext.setEntry( entry );
502         }
503         else
504         {
505             Iterator<String> list = subentryCache.nameIterator();
506 
507             while ( list.hasNext() )
508             {
509                 String subentryDnStr = list.next();
510                 LdapDN subentryDn = new LdapDN( subentryDnStr );
511                 LdapDN apDn = ( LdapDN ) subentryDn.clone();
512                 apDn.remove( apDn.size() - 1 );
513                 Subentry subentry = subentryCache.getSubentry( subentryDnStr );
514                 SubtreeSpecification ss = subentry.getSubtreeSpecification();
515 
516                 if ( evaluator.evaluate( ss, apDn, name, entry ) )
517                 {
518                     EntryAttribute operational;
519 
520                     if ( subentry.isAccessControlSubentry() )
521                     {
522                         operational = entry.get( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT );
523 
524                         if ( operational == null )
525                         {
526                             operational = new DefaultServerAttribute( atRegistry
527                                 .lookup( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ) );
528                             entry.put( operational );
529                         }
530 
531                         operational.add( subentryDn.toString() );
532                     }
533 
534                     if ( subentry.isSchemaSubentry() )
535                     {
536                         operational = entry.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT );
537 
538                         if ( operational == null )
539                         {
540                             operational = new DefaultServerAttribute( atRegistry
541                                 .lookup( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ) );
542                             entry.put( operational );
543                         }
544 
545                         operational.add( subentryDn.toString() );
546                     }
547 
548                     if ( subentry.isCollectiveSubentry() )
549                     {
550                         operational = entry.get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT );
551 
552                         if ( operational == null )
553                         {
554                             operational = new DefaultServerAttribute( atRegistry
555                                 .lookup( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ) );
556                             entry.put( operational );
557                         }
558 
559                         operational.add( subentryDn.toString() );
560                     }
561 
562                     if ( subentry.isTriggerSubentry() )
563                     {
564                         operational = entry.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT );
565 
566                         if ( operational == null )
567                         {
568                             operational = new DefaultServerAttribute( atRegistry
569                                 .lookup( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ) );
570                             entry.put( operational );
571                         }
572 
573                         operational.add( subentryDn.toString() );
574                     }
575                 }
576             }
577 
578             // TODO why are we doing this here if we got the entry from the 
579             // opContext in the first place - got to look into this 
580             addContext.setEntry( entry );
581 
582             next.add( addContext );
583         }
584     }
585 
586 
587     // -----------------------------------------------------------------------
588     // Methods dealing subentry deletion
589     // -----------------------------------------------------------------------
590 
591     public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
592     {
593         LdapDN name = opContext.getDn();
594         ServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
595         EntryAttribute objectClasses = entry.get( objectClassType );
596 
597         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
598         {
599             SubtreeSpecification ss = subentryCache.removeSubentry( name.toNormName() ).getSubtreeSpecification();
600             next.delete( opContext );
601 
602             /* ----------------------------------------------------------------
603              * Find the baseDn for the subentry and use that to search the tree
604              * for all entries included by the subtreeSpecification.  Then we
605              * check the entry for subentry operational attribute that contain
606              * the DN of the subentry.  These are the subentry operational
607              * attributes we remove from the entry in a modify operation.
608              * ----------------------------------------------------------------
609              */
610             LdapDN apName = ( LdapDN ) name.clone();
611             apName.remove( name.size() - 1 );
612             LdapDN baseDn = ( LdapDN ) apName.clone();
613             baseDn.addAll( ss.getBase() );
614 
615             ExprNode filter = new PresenceNode( oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT ) );
616             SearchControls controls = new SearchControls();
617             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
618             controls.setReturningAttributes( new String[]
619                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
620 
621             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( opContext.getSession(),
622                 baseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
623 
624             while ( subentries.next() )
625             {
626                 ServerEntry candidate = subentries.get();
627                 LdapDN dn = new LdapDN( candidate.getDn() );
628                 dn.normalize( atRegistry.getNormalizerMapping() );
629 
630                 if ( evaluator.evaluate( ss, apName, dn, candidate ) )
631                 {
632                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
633                         getOperationalModsForRemove( name, candidate ) ) );
634                 }
635             }
636         }
637         else
638         {
639             next.delete( opContext );
640         }
641     }
642 
643 
644     // -----------------------------------------------------------------------
645     // Methods dealing subentry name changes
646     // -----------------------------------------------------------------------
647 
648     /**
649      * Checks to see if an entry being renamed has a descendant that is an
650      * administrative point.
651      *
652      * @param name the name of the entry which is used as the search base
653      * @return true if name is an administrative point or one of its descendants
654      * are, false otherwise
655      * @throws Exception if there are errors while searching the directory
656      */
657     private boolean hasAdministrativeDescendant( OperationContext opContext, LdapDN name ) throws Exception
658     {
659         ExprNode filter = new PresenceNode( "administrativeRole" );
660         SearchControls controls = new SearchControls();
661         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
662         EntryFilteringCursor aps = nexus.search( new SearchOperationContext( opContext.getSession(), name,
663             AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
664 
665         if ( aps.next() )
666         {
667             aps.close();
668             return true;
669         }
670 
671         return false;
672     }
673 
674 
675     private List<Modification> getModsOnEntryRdnChange( Name oldName, Name newName, ServerEntry entry )
676         throws Exception
677     {
678         List<Modification> modList = new ArrayList<Modification>();
679 
680         /*
681          * There are two different situations warranting action.  Firt if
682          * an ss evalutating to true with the old name no longer evalutates
683          * to true with the new name.  This would be caused by specific chop
684          * exclusions that effect the new name but did not effect the old
685          * name. In this case we must remove subentry operational attribute
686          * values associated with the dn of that subentry.
687          *
688          * In the second case an ss selects the entry with the new name when
689          * it did not previously with the old name.  Again this situation
690          * would be caused by chop exclusions. In this case we must add subentry
691          * operational attribute values with the dn of this subentry.
692          */
693         Iterator<String> subentries = subentryCache.nameIterator();
694 
695         while ( subentries.hasNext() )
696         {
697             String subentryDn = subentries.next();
698             Name apDn = new LdapDN( subentryDn );
699             apDn.remove( apDn.size() - 1 );
700             SubtreeSpecification ss = subentryCache.getSubentry( subentryDn ).getSubtreeSpecification();
701             boolean isOldNameSelected = evaluator.evaluate( ss, apDn, oldName, entry );
702             boolean isNewNameSelected = evaluator.evaluate( ss, apDn, newName, entry );
703 
704             if ( isOldNameSelected == isNewNameSelected )
705             {
706                 continue;
707             }
708 
709             // need to remove references to the subentry
710             if ( isOldNameSelected && !isNewNameSelected )
711             {
712                 for ( String aSUBENTRY_OPATTRS : SUBENTRY_OPATTRS )
713                 {
714                     ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE;
715                     EntryAttribute opAttr = entry.get( aSUBENTRY_OPATTRS );
716 
717                     if ( opAttr != null )
718                     {
719                         opAttr = ( ServerAttribute ) opAttr.clone();
720                         opAttr.remove( subentryDn );
721 
722                         if ( opAttr.size() < 1 )
723                         {
724                             op = ModificationOperation.REMOVE_ATTRIBUTE;
725                         }
726 
727                         modList.add( new ServerModification( op, opAttr ) );
728                     }
729                 }
730             }
731             // need to add references to the subentry
732             else if ( isNewNameSelected && !isOldNameSelected )
733             {
734                 for ( String aSUBENTRY_OPATTRS : SUBENTRY_OPATTRS )
735                 {
736                     ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE;
737                     ServerAttribute opAttr = new DefaultServerAttribute( aSUBENTRY_OPATTRS, atRegistry
738                         .lookup( aSUBENTRY_OPATTRS ) );
739                     opAttr.add( subentryDn );
740                     modList.add( new ServerModification( op, opAttr ) );
741                 }
742             }
743         }
744 
745         return modList;
746     }
747 
748 
749     public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
750     {
751         LdapDN name = opContext.getDn();
752 
753         ServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
754 
755         EntryAttribute objectClasses = entry.get( objectClassType );
756 
757         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
758         {
759             Subentry subentry = subentryCache.getSubentry( name.toNormName() );
760             SubtreeSpecification ss = subentry.getSubtreeSpecification();
761             LdapDN apName = ( LdapDN ) name.clone();
762             apName.remove( apName.size() - 1 );
763             LdapDN baseDn = ( LdapDN ) apName.clone();
764             baseDn.addAll( ss.getBase() );
765             LdapDN newName = ( LdapDN ) name.clone();
766             newName.remove( newName.size() - 1 );
767 
768             newName.add( opContext.getNewRdn() );
769 
770             String newNormName = newName.toNormName();
771             subentryCache.setSubentry( newNormName, ss, subentry.getTypes() );
772             next.rename( opContext );
773 
774             subentry = subentryCache.getSubentry( newNormName );
775             ExprNode filter = new PresenceNode( oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT ) );
776             SearchControls controls = new SearchControls();
777             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
778             controls.setReturningAttributes( new String[]
779                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
780             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( opContext.getSession(),
781                 baseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
782 
783             while ( subentries.next() )
784             {
785                 ServerEntry candidate = subentries.get();
786                 LdapDN dn = candidate.getDn();
787                 dn.normalize( atRegistry.getNormalizerMapping() );
788 
789 
790                 if ( evaluator.evaluate( ss, apName, dn, candidate ) )
791                 {
792                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
793                         getOperationalModsForReplace( name, newName, subentry, candidate ) ) );
794                 }
795             }
796         }
797         else
798         {
799             if ( hasAdministrativeDescendant( opContext, name ) )
800             {
801                 String msg = "Will not allow rename operation on entries with administrative descendants.";
802                 LOG.warn( msg );
803                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOT_ALLOWED_ON_RDN );
804             }
805 
806             next.rename( opContext );
807 
808             // calculate the new DN now for use below to modify subentry operational
809             // attributes contained within this regular entry with name changes
810             LdapDN newName = ( LdapDN ) name.clone();
811             newName.remove( newName.size() - 1 );
812             newName.add( opContext.getNewRdn() );
813             newName.normalize( atRegistry.getNormalizerMapping() );
814             List<Modification> mods = getModsOnEntryRdnChange( name, newName, entry );
815 
816             if ( mods.size() > 0 )
817             {
818                 nexus.modify( new ModifyOperationContext( opContext.getSession(), newName, mods ) );
819             }
820         }
821     }
822 
823 
824     public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws Exception
825     {
826         LdapDN oriChildName = opContext.getDn();
827         LdapDN parent = opContext.getParent();
828 
829         ServerEntry entry = opContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
830 
831         EntryAttribute objectClasses = entry.get( objectClassType );
832 
833         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
834         {
835             Subentry subentry = subentryCache.getSubentry( oriChildName.toNormName() );
836             SubtreeSpecification ss = subentry.getSubtreeSpecification();
837             LdapDN apName = ( LdapDN ) oriChildName.clone();
838             apName.remove( apName.size() - 1 );
839             LdapDN baseDn = ( LdapDN ) apName.clone();
840             baseDn.addAll( ss.getBase() );
841             LdapDN newName = ( LdapDN ) parent.clone();
842             newName.remove( newName.size() - 1 );
843 
844             newName.add( opContext.getNewRdn() );
845 
846             String newNormName = newName.toNormName();
847             subentryCache.setSubentry( newNormName, ss, subentry.getTypes() );
848             next.moveAndRename( opContext );
849 
850             subentry = subentryCache.getSubentry( newNormName );
851 
852             ExprNode filter = new PresenceNode( oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT ) );
853             SearchControls controls = new SearchControls();
854             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
855             controls.setReturningAttributes( new String[]
856                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
857             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( opContext.getSession(),
858                 baseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
859 
860             while ( subentries.next() )
861             {
862                 ServerEntry candidate = subentries.get();
863                 LdapDN dn = candidate.getDn();
864                 dn.normalize( atRegistry.getNormalizerMapping() );
865 
866                 if ( evaluator.evaluate( ss, apName, dn, candidate ) )
867                 {
868                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
869                         getOperationalModsForReplace( oriChildName, newName, subentry, candidate ) ) );
870                 }
871             }
872         }
873         else
874         {
875             if ( hasAdministrativeDescendant( opContext, oriChildName ) )
876             {
877                 String msg = "Will not allow rename operation on entries with administrative descendants.";
878                 LOG.warn( msg );
879                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOT_ALLOWED_ON_RDN );
880             }
881 
882             next.moveAndRename( opContext );
883 
884             // calculate the new DN now for use below to modify subentry operational
885             // attributes contained within this regular entry with name changes
886             LdapDN newName = ( LdapDN ) parent.clone();
887             newName.add( opContext.getNewRdn() );
888             newName.normalize( atRegistry.getNormalizerMapping() );
889             List<Modification> mods = getModsOnEntryRdnChange( oriChildName, newName, entry );
890 
891             if ( mods.size() > 0 )
892             {
893                 nexus.modify( new ModifyOperationContext( opContext.getSession(), newName, mods ) );
894             }
895         }
896     }
897 
898 
899     public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
900     {
901         LdapDN oriChildName = opContext.getDn();
902         LdapDN newParentName = opContext.getParent();
903 
904         ServerEntry entry = opContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
905 
906         EntryAttribute objectClasses = entry.get( SchemaConstants.OBJECT_CLASS_AT );
907 
908         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
909         {
910             Subentry subentry = subentryCache.getSubentry( oriChildName.toString() );
911             SubtreeSpecification ss = subentry.getSubtreeSpecification();
912             LdapDN apName = ( LdapDN ) oriChildName.clone();
913             apName.remove( apName.size() - 1 );
914             LdapDN baseDn = ( LdapDN ) apName.clone();
915             baseDn.addAll( ss.getBase() );
916             LdapDN newName = ( LdapDN ) newParentName.clone();
917             newName.remove( newName.size() - 1 );
918             newName.add( newParentName.get( newParentName.size() - 1 ) );
919 
920             String newNormName = newName.toNormName();
921             subentryCache.setSubentry( newNormName, ss, subentry.getTypes() );
922             next.move( opContext );
923 
924             subentry = subentryCache.getSubentry( newNormName );
925 
926             ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
927             SearchControls controls = new SearchControls();
928             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
929             controls.setReturningAttributes( new String[]
930                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
931             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( opContext.getSession(),
932                 baseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
933 
934             while ( subentries.next() )
935             {
936                 ServerEntry candidate = subentries.get();
937                 LdapDN dn = candidate.getDn();
938                 dn.normalize( atRegistry.getNormalizerMapping() );
939 
940                 if ( evaluator.evaluate( ss, apName, dn, candidate ) )
941                 {
942                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
943                         getOperationalModsForReplace( oriChildName, newName, subentry, candidate ) ) );
944                 }
945             }
946         }
947         else
948         {
949             if ( hasAdministrativeDescendant( opContext, oriChildName ) )
950             {
951                 String msg = "Will not allow rename operation on entries with administrative descendants.";
952                 LOG.warn( msg );
953                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOT_ALLOWED_ON_RDN );
954             }
955 
956             next.move( opContext );
957 
958             // calculate the new DN now for use below to modify subentry operational
959             // attributes contained within this regular entry with name changes
960             LdapDN newName = ( LdapDN ) newParentName.clone();
961             newName.add( oriChildName.get( oriChildName.size() - 1 ) );
962             List<Modification> mods = getModsOnEntryRdnChange( oriChildName, newName, entry );
963 
964             if ( mods.size() > 0 )
965             {
966                 nexus.modify( new ModifyOperationContext( opContext.getSession(), newName, mods ) );
967             }
968         }
969     }
970 
971 
972     // -----------------------------------------------------------------------
973     // Methods dealing subentry modification
974     // -----------------------------------------------------------------------
975 
976     private int getSubentryTypes( ServerEntry entry, List<Modification> mods ) throws Exception
977     {
978         ServerAttribute ocFinalState = ( ServerAttribute ) entry.get( SchemaConstants.OBJECT_CLASS_AT ).clone();
979 
980         for ( Modification mod : mods )
981         {
982             if ( mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) )
983             {
984                 switch ( mod.getOperation() )
985                 {
986                     case ADD_ATTRIBUTE:
987                         for ( Value<?> value : ( ServerAttribute ) mod.getAttribute() )
988                         {
989                             ocFinalState.add( ( String ) value.get() );
990                         }
991 
992                         break;
993 
994                     case REMOVE_ATTRIBUTE:
995                         for ( Value<?> value : ( ServerAttribute ) mod.getAttribute() )
996                         {
997                             ocFinalState.remove( ( String ) value.get() );
998                         }
999 
1000                         break;
1001 
1002                     case REPLACE_ATTRIBUTE:
1003                         ocFinalState = ( ServerAttribute ) mod.getAttribute();
1004                         break;
1005                 }
1006             }
1007         }
1008 
1009         ServerEntry attrs = new DefaultServerEntry( registries, LdapDN.EMPTY_LDAPDN );
1010         attrs.put( ocFinalState );
1011         return getSubentryTypes( attrs );
1012     }
1013 
1014 
1015     public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
1016     {
1017         LdapDN name = opContext.getDn();
1018         List<Modification> mods = opContext.getModItems();
1019 
1020         ServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
1021 
1022         ServerEntry oldEntry = ( ServerEntry ) entry.clone();
1023         EntryAttribute objectClasses = entry.get( objectClassType );
1024         boolean isSubtreeSpecificationModification = false;
1025         Modification subtreeMod = null;
1026 
1027         for ( Modification mod : mods )
1028         {
1029             if ( SchemaConstants.SUBTREE_SPECIFICATION_AT.equalsIgnoreCase( mod.getAttribute().getId() ) )
1030             {
1031                 isSubtreeSpecificationModification = true;
1032                 subtreeMod = mod;
1033             }
1034         }
1035 
1036         if ( objectClasses.contains( SchemaConstants.SUBENTRY_OC ) && isSubtreeSpecificationModification )
1037         {
1038             SubtreeSpecification ssOld = subentryCache.removeSubentry( name.toString() ).getSubtreeSpecification();
1039             SubtreeSpecification ssNew;
1040 
1041             try
1042             {
1043                 ssNew = ssParser.parse( ( ( ServerAttribute ) subtreeMod.getAttribute() ).getString() );
1044             }
1045             catch ( Exception e )
1046             {
1047                 String msg = "failed to parse the new subtreeSpecification";
1048                 LOG.error( msg, e );
1049                 throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX );
1050             }
1051 
1052             subentryCache.setSubentry( name.toNormName(), ssNew, getSubentryTypes( entry, mods ) );
1053             next.modify( opContext );
1054 
1055             // search for all entries selected by the old SS and remove references to subentry
1056             LdapDN apName = ( LdapDN ) name.clone();
1057             apName.remove( apName.size() - 1 );
1058             LdapDN oldBaseDn = ( LdapDN ) apName.clone();
1059             oldBaseDn.addAll( ssOld.getBase() );
1060             ExprNode filter = new PresenceNode( oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT ) );
1061             SearchControls controls = new SearchControls();
1062             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
1063             controls.setReturningAttributes( new String[]
1064                 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } );
1065             EntryFilteringCursor subentries = nexus.search( new SearchOperationContext( opContext.getSession(),
1066                 oldBaseDn, AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
1067 
1068             while ( subentries.next() )
1069             {
1070                 ServerEntry candidate = subentries.get();
1071                 LdapDN dn = candidate.getDn();
1072                 dn.normalize( atRegistry.getNormalizerMapping() );
1073 
1074                 if ( evaluator.evaluate( ssOld, apName, dn, candidate ) )
1075                 {
1076                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
1077                         getOperationalModsForRemove( name, candidate ) ) );
1078                 }
1079             }
1080 
1081             // search for all selected entries by the new SS and add references to subentry
1082             Subentry subentry = subentryCache.getSubentry( name.toNormName() );
1083             ServerEntry operational = getSubentryOperatationalAttributes( name, subentry );
1084             LdapDN newBaseDn = ( LdapDN ) apName.clone();
1085             newBaseDn.addAll( ssNew.getBase() );
1086             subentries = nexus.search( new SearchOperationContext( opContext.getSession(), newBaseDn,
1087                 AliasDerefMode.NEVER_DEREF_ALIASES, filter, controls ) );
1088             
1089             while ( subentries.next() )
1090             {
1091                 ServerEntry candidate = subentries.get();
1092                 LdapDN dn = candidate.getDn();
1093                 dn.normalize( atRegistry.getNormalizerMapping() );
1094 
1095                 if ( evaluator.evaluate( ssNew, apName, dn, candidate ) )
1096                 {
1097                     nexus.modify( new ModifyOperationContext( opContext.getSession(), dn, 
1098                         getOperationalModsForAdd( candidate, operational ) ) );
1099                 }
1100             }
1101         }
1102         else
1103         {
1104             next.modify( opContext );
1105 
1106             if ( !objectClasses.contains( SchemaConstants.SUBENTRY_OC ) )
1107             {
1108                 ServerEntry newEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
1109 
1110                 List<Modification> subentriesOpAttrMods = getModsOnEntryModification( name, oldEntry, newEntry );
1111 
1112                 if ( subentriesOpAttrMods.size() > 0 )
1113                 {
1114                     nexus.modify( new ModifyOperationContext( opContext.getSession(), name, subentriesOpAttrMods ) );
1115                 }
1116             }
1117         }
1118     }
1119 
1120 
1121     // -----------------------------------------------------------------------
1122     // Utility Methods
1123     // -----------------------------------------------------------------------
1124 
1125     private List<Modification> getOperationalModsForReplace( Name oldName, Name newName, Subentry subentry,
1126         ServerEntry entry ) throws Exception
1127     {
1128         List<Modification> modList = new ArrayList<Modification>();
1129 
1130         ServerAttribute operational;
1131 
1132         if ( subentry.isAccessControlSubentry() )
1133         {
1134             operational = ( ServerAttribute ) entry.get( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ).clone();
1135 
1136             if ( operational == null )
1137             {
1138                 operational = new DefaultServerAttribute( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT, atRegistry
1139                     .lookup( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ) );
1140                 operational.add( newName.toString() );
1141             }
1142             else
1143             {
1144                 operational.remove( oldName.toString() );
1145                 operational.add( newName.toString() );
1146             }
1147 
1148             modList.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) );
1149         }
1150 
1151         if ( subentry.isSchemaSubentry() )
1152         {
1153             operational = ( ServerAttribute ) entry.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).clone();
1154 
1155             if ( operational == null )
1156             {
1157                 operational = new DefaultServerAttribute( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, atRegistry
1158                     .lookup( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ) );
1159                 operational.add( newName.toString() );
1160             }
1161             else
1162             {
1163                 operational.remove( oldName.toString() );
1164                 operational.add( newName.toString() );
1165             }
1166 
1167             modList.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) );
1168         }
1169 
1170         if ( subentry.isCollectiveSubentry() )
1171         {
1172             operational = ( ServerAttribute ) entry.get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ).clone();
1173 
1174             if ( operational == null )
1175             {
1176                 operational = new DefaultServerAttribute( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT,
1177                     atRegistry.lookup( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ) );
1178                 operational.add( newName.toString() );
1179             }
1180             else
1181             {
1182                 operational.remove( oldName.toString() );
1183                 operational.add( newName.toString() );
1184             }
1185 
1186             modList.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) );
1187         }
1188 
1189         if ( subentry.isTriggerSubentry() )
1190         {
1191             operational = ( ServerAttribute ) entry.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ).clone();
1192 
1193             if ( operational == null )
1194             {
1195                 operational = new DefaultServerAttribute( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT, atRegistry
1196                     .lookup( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ) );
1197                 operational.add( newName.toString() );
1198             }
1199             else
1200             {
1201                 operational.remove( oldName.toString() );
1202                 operational.add( newName.toString() );
1203             }
1204 
1205             modList.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) );
1206         }
1207 
1208         return modList;
1209     }
1210 
1211 
1212     /**
1213      * Gets the subschema operational attributes to be added to or removed from
1214      * an entry selected by a subentry's subtreeSpecification.
1215      *
1216      * @param name the normalized distinguished name of the subentry (the value of op attrs)
1217      * @param subentry the subentry to get attributes from
1218      * @return the set of attributes to be added or removed from entries
1219      */
1220     private ServerEntry getSubentryOperatationalAttributes( LdapDN name, Subentry subentry ) throws Exception
1221     {
1222         ServerEntry operational = new DefaultServerEntry( registries, name );
1223 
1224         if ( subentry.isAccessControlSubentry() )
1225         {
1226             if ( operational.get( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ) == null )
1227             {
1228                 operational.put( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT, name.toString() );
1229             }
1230             else
1231             {
1232                 operational.get( SchemaConstants.ACCESS_CONTROL_SUBENTRIES_AT ).add( name.toString() );
1233             }
1234         }
1235         if ( subentry.isSchemaSubentry() )
1236         {
1237             if ( operational.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ) == null )
1238             {
1239                 operational.put( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, name.toString() );
1240             }
1241             else
1242             {
1243                 operational.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).add( name.toString() );
1244             }
1245         }
1246         if ( subentry.isCollectiveSubentry() )
1247         {
1248             if ( operational.get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ) == null )
1249             {
1250                 operational.put( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT, name.toString() );
1251             }
1252             else
1253             {
1254                 operational.get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ).add( name.toString() );
1255             }
1256         }
1257         if ( subentry.isTriggerSubentry() )
1258         {
1259             if ( operational.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ) == null )
1260             {
1261                 operational.put( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT, name.toString() );
1262             }
1263             else
1264             {
1265                 operational.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT ).add( name.toString() );
1266             }
1267         }
1268 
1269         return operational;
1270     }
1271 
1272 
1273     /**
1274      * Calculates the subentry operational attributes to remove from a candidate
1275      * entry selected by a subtreeSpecification.  When we remove a subentry we
1276      * must remove the operational attributes in the entries that were once selected
1277      * by the subtree specification of that subentry.  To do so we must perform
1278      * a modify operation with the set of modifications to perform.  This method
1279      * calculates those modifications.
1280      *
1281      * @param subentryDn the distinguished name of the subentry
1282      * @param candidate the candidate entry to removed from the
1283      * @return the set of modifications required to remove an entry's reference to
1284      * a subentry
1285      */
1286     private List<Modification> getOperationalModsForRemove( LdapDN subentryDn, ServerEntry candidate )
1287         throws Exception
1288     {
1289         List<Modification> modList = new ArrayList<Modification>();
1290         String dn = subentryDn.toNormName();
1291 
1292         for ( String opAttrId : SUBENTRY_OPATTRS )
1293         {
1294             EntryAttribute opAttr = candidate.get( opAttrId );
1295 
1296             if ( ( opAttr != null ) && opAttr.contains( dn ) )
1297             {
1298                 AttributeType attributeType = atRegistry.lookup( opAttrId );
1299                 ServerAttribute attr = new DefaultServerAttribute( opAttrId, attributeType, dn );
1300                 modList.add( new ServerModification( ModificationOperation.REMOVE_ATTRIBUTE, attr ) );
1301             }
1302         }
1303 
1304         return modList;
1305     }
1306 
1307 
1308     /**
1309      * Calculates the subentry operational attributes to add or replace from
1310      * a candidate entry selected by a subtree specification.  When a subentry
1311      * is added or it's specification is modified some entries must have new
1312      * operational attributes added to it to point back to the associated
1313      * subentry.  To do so a modify operation must be performed on entries
1314      * selected by the subtree specification.  This method calculates the
1315      * modify operation to be performed on the entry.
1316      *
1317      * @param entry the entry being modified
1318      * @param operational the set of operational attributes supported by the AP
1319      * of the subentry
1320      * @return the set of modifications needed to update the entry
1321      * @throws Exception if there are probelms accessing modification items
1322      */
1323     public List<Modification> getOperationalModsForAdd( ServerEntry entry, ServerEntry operational )
1324         throws Exception
1325     {
1326         List<Modification> modList = new ArrayList<Modification>();
1327 
1328         for ( AttributeType attributeType : operational.getAttributeTypes() )
1329         {
1330             ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE;
1331             EntryAttribute result = new DefaultServerAttribute( attributeType );
1332             EntryAttribute opAttrAdditions = operational.get( attributeType );
1333             EntryAttribute opAttrInEntry = entry.get( attributeType );
1334 
1335             for ( Value<?> value : opAttrAdditions )
1336             {
1337                 result.add( value );
1338             }
1339 
1340             if ( opAttrInEntry != null && opAttrInEntry.size() > 0 )
1341             {
1342                 for ( Value<?> value : opAttrInEntry )
1343                 {
1344                     result.add( value );
1345                 }
1346             }
1347             else
1348             {
1349                 op = ModificationOperation.ADD_ATTRIBUTE;
1350             }
1351 
1352             modList.add( new ServerModification( op, result ) );
1353         }
1354 
1355         return modList;
1356     }
1357 
1358     /**
1359      * SearchResultFilter used to filter out subentries based on objectClass values.
1360      */
1361     public class HideSubentriesFilter implements EntryFilter
1362     {
1363         public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry )
1364             throws Exception
1365         {
1366             String dn = entry.getDn().getNormName();
1367 
1368             // see if we can get a match without normalization
1369             if ( subentryCache.hasSubentry( dn ) )
1370             {
1371                 return false;
1372             }
1373 
1374             // see if we can use objectclass if present
1375             EntryAttribute objectClasses = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1376 
1377             if ( objectClasses != null )
1378             {
1379                 return !objectClasses.contains( SchemaConstants.SUBENTRY_OC );
1380             }
1381 
1382             LdapDN ndn = new LdapDN( dn );
1383             ndn.normalize( atRegistry.getNormalizerMapping() );
1384             String normalizedDn = ndn.toString();
1385             return !subentryCache.hasSubentry( normalizedDn );
1386         }
1387     }
1388 
1389     /**
1390      * SearchResultFilter used to filter out normal entries but shows subentries based on 
1391      * objectClass values.
1392      */
1393     public class HideEntriesFilter implements EntryFilter
1394     {
1395         public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry )
1396             throws Exception
1397         {
1398             String dn = entry.getDn().getNormName();
1399 
1400             // see if we can get a match without normalization
1401             if ( subentryCache.hasSubentry( dn ) )
1402             {
1403                 return true;
1404             }
1405 
1406             // see if we can use objectclass if present
1407             EntryAttribute objectClasses = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1408 
1409             if ( objectClasses != null )
1410             {
1411                 return objectClasses.contains( SchemaConstants.SUBENTRY_OC );
1412             }
1413 
1414             LdapDN ndn = new LdapDN( dn );
1415             ndn.normalize( atRegistry.getNormalizerMapping() );
1416             return subentryCache.hasSubentry( ndn.toNormName() );
1417         }
1418     }
1419 
1420 
1421     private List<Modification> getModsOnEntryModification( LdapDN name, ServerEntry oldEntry, ServerEntry newEntry )
1422         throws Exception
1423     {
1424         List<Modification> modList = new ArrayList<Modification>();
1425 
1426         Iterator<String> subentries = subentryCache.nameIterator();
1427 
1428         while ( subentries.hasNext() )
1429         {
1430             String subentryDn = subentries.next();
1431             Name apDn = new LdapDN( subentryDn );
1432             apDn.remove( apDn.size() - 1 );
1433             SubtreeSpecification ss = subentryCache.getSubentry( subentryDn ).getSubtreeSpecification();
1434             boolean isOldEntrySelected = evaluator.evaluate( ss, apDn, name, oldEntry );
1435             boolean isNewEntrySelected = evaluator.evaluate( ss, apDn, name, newEntry );
1436 
1437             if ( isOldEntrySelected == isNewEntrySelected )
1438             {
1439                 continue;
1440             }
1441 
1442             // need to remove references to the subentry
1443             if ( isOldEntrySelected && !isNewEntrySelected )
1444             {
1445                 for ( String aSUBENTRY_OPATTRS : SUBENTRY_OPATTRS )
1446                 {
1447                     ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE;
1448                     EntryAttribute opAttr = oldEntry.get( aSUBENTRY_OPATTRS );
1449 
1450                     if ( opAttr != null )
1451                     {
1452                         opAttr = ( ServerAttribute ) opAttr.clone();
1453                         opAttr.remove( subentryDn );
1454 
1455                         if ( opAttr.size() < 1 )
1456                         {
1457                             op = ModificationOperation.REMOVE_ATTRIBUTE;
1458                         }
1459 
1460                         modList.add( new ServerModification( op, opAttr ) );
1461                     }
1462                 }
1463             }
1464             // need to add references to the subentry
1465             else if ( isNewEntrySelected && !isOldEntrySelected )
1466             {
1467                 for ( String attribute : SUBENTRY_OPATTRS )
1468                 {
1469                     ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE;
1470                     AttributeType type = atRegistry.lookup( attribute );
1471                     ServerAttribute opAttr = new DefaultServerAttribute( attribute, type );
1472                     opAttr.add( subentryDn );
1473                     modList.add( new ServerModification( op, opAttr ) );
1474                 }
1475             }
1476         }
1477 
1478         return modList;
1479     }
1480 
1481 }