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