View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.subtree;
18  
19  
20  import org.apache.ldap.server.DirectoryServiceConfiguration;
21  import org.apache.ldap.server.interceptor.BaseInterceptor;
22  import org.apache.ldap.server.interceptor.NextInterceptor;
23  import org.apache.ldap.server.enumeration.SearchResultFilter;
24  import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
25  import org.apache.ldap.server.invocation.InvocationStack;
26  import org.apache.ldap.server.invocation.Invocation;
27  import org.apache.ldap.server.configuration.InterceptorConfiguration;
28  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
29  import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
30  import org.apache.ldap.common.message.SubentryRequestControl;
31  import org.apache.ldap.common.message.ResultCodeEnum;
32  import org.apache.ldap.common.message.LockableAttributesImpl;
33  import org.apache.ldap.common.message.LockableAttributeImpl;
34  import org.apache.ldap.common.filter.*;
35  import org.apache.ldap.common.subtree.SubtreeSpecificationParser;
36  import org.apache.ldap.common.subtree.SubtreeSpecification;
37  import org.apache.ldap.common.name.DnParser;
38  import org.apache.ldap.common.name.LdapName;
39  import org.apache.ldap.common.exception.LdapNoSuchAttributeException;
40  import org.apache.ldap.common.exception.LdapInvalidAttributeValueException;
41  import org.apache.ldap.common.exception.LdapSchemaViolationException;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import javax.naming.ldap.Control;
46  import javax.naming.ldap.LdapContext;
47  import javax.naming.directory.*;
48  import javax.naming.NamingException;
49  import javax.naming.NamingEnumeration;
50  import javax.naming.Name;
51  import java.util.*;
52  
53  
54  /***
55   * The Subentry interceptor service which is responsible for filtering
56   * out subentries on search operations and injecting operational attributes
57   *
58   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
59   * @version $Rev$
60   */
61  public class SubentryService extends BaseInterceptor
62  {
63      /*** the subentry control OID */
64      private static final String SUBENTRY_CONTROL = "1.3.6.1.4.1.4203.1.10.1";
65      /*** the objectClass value for a subentry */
66      private static final String SUBENTRY_OBJECTCLASS = "subentry";
67      /*** the objectClass OID for a subentry */
68      private static final String SUBENTRY_OBJECTCLASS_OID = "2.5.17.0";
69  
70      public static final String AUTONOUMOUS_AREA = "autonomousArea";
71      public static final String AUTONOUMOUS_AREA_SUBENTRY = "autonomousAreaSubentry";
72  
73      public static final String AC_AREA = "accessControlSpecificArea";
74      public static final String AC_INNERAREA = "accessControlInnerArea";
75      public static final String AC_SUBENTRY = "accessControlSubentries";
76  
77      public static final String SCHEMA_AREA = "subschemaAdminSpecificArea";
78      public static final String SCHEMA_AREA_SUBENTRY = "subschemaSubentry";
79  
80      public static final String COLLECTIVE_AREA = "collectiveAttributeSpecificArea";
81      public static final String COLLECTIVE_ATTRIBUTE_SUBENTRIES = "collectiveAttributeSubentries";
82  
83      public static final String COLLECTIVE_INNERAREA = "collectiveAttributeInnerArea";
84  
85      public static final String[] SUBENTRY_OPATTRS = {
86          AUTONOUMOUS_AREA_SUBENTRY,
87          AC_SUBENTRY,
88          SCHEMA_AREA_SUBENTRY,
89          COLLECTIVE_ATTRIBUTE_SUBENTRIES
90      };
91  
92      /***
93       * the search result filter to filter out subentries based on objectClass values.
94       */
95      private static final SearchResultFilter SUBENTRY_FILTER = new SearchResultFilter()
96      {
97          public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
98          {
99              Attribute objectClasses = result.getAttributes().get( "objectClass" );
100 
101             if ( objectClasses == null )
102             {
103                 return true;
104             }
105 
106             return !( objectClasses.contains(SUBENTRY_OBJECTCLASS) || objectClasses.contains(SUBENTRY_OBJECTCLASS_OID) );
107         }
108     };
109 
110     private static final Logger log = LoggerFactory.getLogger( SubentryService.class );
111 
112     /*** the hash mapping the DN of a subentry to its SubtreeSpecification */
113     private final Map subtrees = new HashMap();
114     private DirectoryServiceConfiguration factoryCfg;
115     private DnParser dnParser;
116     private SubtreeSpecificationParser ssParser;
117     private SubtreeEvaluator evaluator;
118     private DirectoryPartitionNexus nexus;
119 
120 
121     public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
122     {
123         super.init( factoryCfg, cfg );
124         this.nexus = factoryCfg.getPartitionNexus();
125         this.factoryCfg = factoryCfg;
126         ConcreteNameComponentNormalizer ncn = new ConcreteNameComponentNormalizer(
127                 factoryCfg.getGlobalRegistries().getAttributeTypeRegistry() );
128         ssParser = new SubtreeSpecificationParser( ncn );
129         dnParser = new DnParser( ncn );
130         evaluator = new SubtreeEvaluator( factoryCfg.getGlobalRegistries().getOidRegistry() );
131 
132         // prepare to find all subentries in all namingContexts
133         Iterator suffixes = this.nexus.listSuffixes( true );
134         ExprNode filter = new SimpleNode( "objectclass", "subentry", LeafNode.EQUALITY );
135         SearchControls controls = new SearchControls();
136         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
137         controls.setReturningAttributes( new String[] { "subtreeSpecification" } );
138 
139         // search each namingContext for subentries
140         while ( suffixes.hasNext() )
141         {
142             Name suffix = dnParser.parse( ( String ) suffixes.next() );
143             NamingEnumeration subentries = nexus.search( suffix, factoryCfg.getEnvironment(), filter, controls );
144             while ( subentries.hasMore() )
145             {
146                 SearchResult result = ( SearchResult ) subentries.next();
147                 Attributes subentry = result.getAttributes();
148                 String dn = result.getName();
149                 String subtree = ( String ) subentry.get( "subtreeSpecification" ).get();
150                 SubtreeSpecification ss;
151 
152                 try
153                 {
154                     ss = ssParser.parse( subtree );
155                 }
156                 catch ( Exception e )
157                 {
158                     log.warn( "Failed while parsing subtreeSpecification for " + dn );
159                     continue;
160                 }
161 
162                 subtrees.put( dnParser.parse( dn ).toString(), ss );
163             }
164         }
165     }
166 
167 
168     // -----------------------------------------------------------------------
169     // Methods/Code dealing with Subentry Visibility
170     // -----------------------------------------------------------------------
171 
172 
173     public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
174     {
175         NamingEnumeration e = nextInterceptor.list( base );
176         Invocation invocation = InvocationStack.getInstance().peek();
177 
178         if ( ! isSubentryVisible( invocation ) )
179         {
180             return new SearchResultFilteringEnumeration( e, new SearchControls(), invocation, SUBENTRY_FILTER );
181         }
182 
183         return e;
184     }
185 
186 
187     public NamingEnumeration search( NextInterceptor nextInterceptor, Name base, Map env, ExprNode filter,
188             SearchControls searchCtls ) throws NamingException
189     {
190         NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
191         Invocation invocation = InvocationStack.getInstance().peek();
192 
193         // object scope searches by default return subentries
194         if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
195         {
196             return e;
197         }
198 
199         // for subtree and one level scope we filter
200         if ( ! isSubentryVisible( invocation ) )
201         {
202             return new SearchResultFilteringEnumeration( e, searchCtls, invocation, SUBENTRY_FILTER );
203         }
204 
205         return e;
206     }
207 
208 
209     /***
210      * Checks to see if subentries for the search and list operations should be
211      * made visible based on the availability of the search request control
212      *
213      * @param invocation
214      * @return true if subentries should be visible, false otherwise
215      * @throws NamingException if there are problems accessing request controls
216      */
217     private boolean isSubentryVisible( Invocation invocation ) throws NamingException
218     {
219         Control[] reqControls = ( ( LdapContext ) invocation.getCaller() ).getRequestControls();
220 
221         if ( reqControls == null || reqControls.length <= 0 )
222         {
223             return false;
224         }
225 
226         // check all request controls to see if subentry control is present
227         for ( int ii = 0; ii < reqControls.length; ii++ )
228         {
229             // found the subentry request control so we return its value
230             if ( reqControls[ii].getID().equals( SUBENTRY_CONTROL ) )
231             {
232                 SubentryRequestControl subentryControl = ( SubentryRequestControl ) reqControls[ii];
233                 return subentryControl.getSubentryVisibility();
234             }
235         }
236 
237         return false;
238     }
239 
240 
241     // -----------------------------------------------------------------------
242     // Methods dealing with entry and subentry addition
243     // -----------------------------------------------------------------------
244 
245 
246     /***
247      * Evaluates the set of subentry subtrees upon an entry and returns the
248      * operational subentry attributes that will be added to the entry if
249      * added at the dn specified.
250      *
251      * @param dn the normalized distinguished name of the entry
252      * @param entryAttrs the entry attributes are generated for
253      * @return the set of subentry op attrs for an entry
254      * @throws NamingException if there are problems accessing entry information
255      */
256     public Attributes getSubentryAttributes( Name dn, Attributes entryAttrs ) throws NamingException
257     {
258         Attributes subentryAttrs = new LockableAttributesImpl();
259         Attribute objectClasses = entryAttrs.get( "objectClass" );
260         Iterator list = subtrees.keySet().iterator();
261         while ( list.hasNext() )
262         {
263             String subentryDnStr = ( String ) list.next();
264             Name subentryDn = new LdapName( subentryDnStr );
265             Name apDn = ( Name ) subentryDn.clone();
266             apDn.remove( apDn.size() - 1 );
267             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );
268 
269             if ( evaluator.evaluate( ss, apDn, dn, objectClasses ) )
270             {
271                 Attribute administrativeRole = nexus.lookup( apDn ).get( "administrativeRole" );
272                 NamingEnumeration roles = administrativeRole.getAll();
273                 while ( roles.hasMore() )
274                 {
275                     Attribute operational;
276                     String role = ( String ) roles.next();
277 
278                     if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
279                     {
280                         operational = subentryAttrs.get( AUTONOUMOUS_AREA_SUBENTRY );
281                         if ( operational == null )
282                         {
283                             operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
284                             subentryAttrs.put( operational );
285                         }
286                     }
287                     else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
288                     {
289                         operational = subentryAttrs.get( AC_SUBENTRY );
290                         if ( operational == null )
291                         {
292                             operational = new LockableAttributeImpl( AC_SUBENTRY );
293                             subentryAttrs.put( operational );
294                         }
295                     }
296                     else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
297                     {
298                         operational = subentryAttrs.get( SCHEMA_AREA_SUBENTRY );
299                         if ( operational == null )
300                         {
301                             operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
302                             subentryAttrs.put( operational );
303                         }
304                     }
305                     else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
306                               role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
307                     {
308                         operational = subentryAttrs.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
309                         if ( operational == null )
310                         {
311                             operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
312                             subentryAttrs.put( operational );
313                         }
314                     }
315                     else
316                     {
317                         throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
318                                 + role + "' in administrative point of subentry " + subentryDnStr + ". The values of this attribute"
319                                 + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
320                                 + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
321                                 + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
322                     }
323 
324                     operational.add( subentryDn.toString() );
325                 }
326             }
327         }
328 
329         return subentryAttrs;
330     }
331 
332 
333     public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
334     {
335         Attribute objectClasses = entry.get( "objectClass" );
336 
337         if ( objectClasses.contains( "subentry" ) )
338         {
339             // get the name of the administrative point and its administrativeRole attributes
340             Name apName = ( Name ) normName.clone();
341             apName.remove( normName.size() - 1 );
342             Attributes ap = nexus.lookup( apName );
343             Attribute administrativeRole = ap.get( "administrativeRole" );
344 
345             // check that administrativeRole has something valid in it for us
346             if ( administrativeRole == null || administrativeRole.size() <= 0 )
347             {
348                 throw new LdapNoSuchAttributeException( "Administration point " + apName
349                         + " does not contain an administrativeRole attribute! An"
350                         + " administrativeRole attribute in the administrative point is"
351                         + " required to add a subordinate subentry." );
352             }
353 
354             /* ----------------------------------------------------------------
355              * Build the set of operational attributes to be injected into
356              * entries that are contained within the subtree repesented by this
357              * new subentry.  In the process we make sure the proper roles are
358              * supported by the administrative point to allow the addition of
359              * this new subentry.
360              * ----------------------------------------------------------------
361              */
362             Attributes operational = getSubentryOperatationalAttributes( normName, administrativeRole );
363 
364             /* ----------------------------------------------------------------
365              * Parse the subtreeSpecification of the subentry and add it to the
366              * SubtreeSpecification cache.  If the parse succeeds we continue
367              * to add the entry to the DIT.  Thereafter we search out entries
368              * to modify the subentry operational attributes of.
369              * ----------------------------------------------------------------
370              */
371             String subtree = ( String ) entry.get( "subtreeSpecification" ).get();
372             SubtreeSpecification ss;
373             try
374             {
375                 ss = ssParser.parse( subtree );
376             }
377             catch ( Exception e )
378             {
379                 String msg = "Failed while parsing subtreeSpecification for " + upName;
380                 log.warn( msg );
381                 throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
382             }
383             subtrees.put( normName.toString(), ss );
384             next.add( upName, normName, entry );
385 
386             /* ----------------------------------------------------------------
387              * Find the baseDn for the subentry and use that to search the tree
388              * while testing each entry returned for inclusion within the
389              * subtree of the subentry's subtreeSpecification.  All included
390              * entries will have their operational attributes merged with the
391              * operational attributes calculated above.
392              * ----------------------------------------------------------------
393              */
394             Name baseDn = ( Name ) apName.clone();
395             baseDn.addAll( ss.getBase() );
396 
397             ExprNode filter = new PresenceNode( "objectclass" );
398             SearchControls controls = new SearchControls();
399             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
400             controls.setReturningAttributes( new String[] { "+", "*" } );
401 
402             NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
403             while ( subentries.hasMore() )
404             {
405                 SearchResult result = ( SearchResult ) subentries.next();
406                 Attributes candidate = result.getAttributes();
407                 Name dn = dnParser.parse( result.getName() );
408 
409                 if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
410                 {
411                     nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
412                 }
413             }
414         }
415         else
416         {
417             Iterator list = subtrees.keySet().iterator();
418             while ( list.hasNext() )
419             {
420                 String subentryDnStr = ( String ) list.next();
421                 Name subentryDn = new LdapName( subentryDnStr );
422                 Name apDn = ( Name ) subentryDn.clone();
423                 apDn.remove( apDn.size() - 1 );
424                 SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );
425 
426                 if ( evaluator.evaluate( ss, apDn, normName, objectClasses ) )
427                 {
428                     Attribute administrativeRole = nexus.lookup( apDn ).get( "administrativeRole" );
429                     NamingEnumeration roles = administrativeRole.getAll();
430                     while ( roles.hasMore() )
431                     {
432                         Attribute operational;
433                         String role = ( String ) roles.next();
434 
435                         if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
436                         {
437                             operational = entry.get( AUTONOUMOUS_AREA_SUBENTRY );
438                             if ( operational == null )
439                             {
440                                 operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
441                                 entry.put( operational );
442                             }
443                         }
444                         else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
445                         {
446                             operational = ( Attribute ) entry.get( AC_SUBENTRY );
447                             if ( operational == null )
448                             {
449                                 operational = new LockableAttributeImpl( AC_SUBENTRY );
450                                 entry.put( operational );
451                             }
452                         }
453                         else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
454                         {
455                             operational = ( Attribute ) entry.get( SCHEMA_AREA_SUBENTRY );
456                             if ( operational == null )
457                             {
458                                 operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
459                                 entry.put( operational );
460                             }
461                         }
462                         else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
463                                   role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
464                         {
465                             operational = ( Attribute ) entry.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
466                             if ( operational == null )
467                             {
468                                 operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
469                                 entry.put( operational );
470                             }
471                         }
472                         else
473                         {
474                             throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
475                                     + role + "' in administrative point of subentry " + subentryDnStr + ". The values of this attribute"
476                                     + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
477                                     + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
478                                     + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
479                         }
480 
481                         operational.add( subentryDn.toString() );
482                     }
483                 }
484             }
485 
486             next.add( upName, normName, entry );
487         }
488     }
489 
490 
491     // -----------------------------------------------------------------------
492     // Methods dealing subentry deletion
493     // -----------------------------------------------------------------------
494 
495 
496     public void delete( NextInterceptor next, Name name ) throws NamingException
497     {
498         Attributes entry = nexus.lookup( name );
499         Attribute objectClasses = entry.get( "objectClass" );
500 
501         if ( objectClasses.contains( "subentry" ) )
502         {
503             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( name.toString() );
504             subtrees.remove( ss );
505             next.delete( name );
506 
507             /* ----------------------------------------------------------------
508              * Find the baseDn for the subentry and use that to search the tree
509              * for all entries included by the subtreeSpecification.  Then we
510              * check the entry for subentry operational attribute that contain
511              * the DN of the subentry.  These are the subentry operational
512              * attributes we remove from the entry in a modify operation.
513              * ----------------------------------------------------------------
514              */
515             Name apName = ( Name ) name.clone();
516             apName.remove( name.size() - 1 );
517             Name baseDn = ( Name ) apName.clone();
518             baseDn.addAll( ss.getBase() );
519 
520             ExprNode filter = new PresenceNode( "objectclass" );
521             SearchControls controls = new SearchControls();
522             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
523             controls.setReturningAttributes( new String[] { "+", "*" } );
524 
525             NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
526             while ( subentries.hasMore() )
527             {
528                 SearchResult result = ( SearchResult ) subentries.next();
529                 Attributes candidate = result.getAttributes();
530                 Name dn = dnParser.parse( result.getName() );
531 
532                 if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
533                 {
534                     nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
535                 }
536             }
537         }
538         else
539         {
540             next.delete( name );
541         }
542     }
543 
544 
545     // -----------------------------------------------------------------------
546     // Methods dealing subentry name changes
547     // -----------------------------------------------------------------------
548 
549 
550     /***
551      * Checks to see if an entry being renamed has a descendant that is an
552      * administrative point.
553      *
554      * @param name the name of the entry which is used as the search base
555      * @return true if name is an administrative point or one of its descendants
556      * are, false otherwise
557      * @throws NamingException if there are errors while searching the directory
558      */
559     private boolean hasAdministrativeDescendant( Name name ) throws NamingException
560     {
561         ExprNode filter = new PresenceNode( "administrativeRole" );
562         SearchControls controls = new SearchControls();
563         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
564         NamingEnumeration aps = nexus.search( name, factoryCfg.getEnvironment(), filter, controls );
565         if ( aps.hasMore() )
566         {
567             aps.close();
568             return true;
569         }
570 
571         return false;
572     }
573 
574 
575     private ModificationItem[] getModsOnEntryRdnChange( Name oldName, Name newName, Attributes entry )
576         throws NamingException
577     {
578         Attribute objectClasses = entry.get( "objectClass" );
579         List modList = new ArrayList();
580 
581         /*
582          * There are two different situations warranting action.  Firt if
583          * an ss evalutating to true with the old name no longer evalutates
584          * to true with the new name.  This would be caused by specific chop
585          * exclusions that effect the new name but did not effect the old
586          * name. In this case we must remove subentry operational attribute
587          * values associated with the dn of that subentry.
588          *
589          * In the second case an ss selects the entry with the new name when
590          * it did not previously with the old name.  Again this situation
591          * would be caused by chop exclusions. In this case we must add subentry
592          * operational attribute values with the dn of this subentry.
593          */
594         Iterator subentries = subtrees.keySet().iterator();
595         while ( subentries.hasNext() )
596         {
597             String subentryDn = ( String ) subentries.next();
598             Name apDn = new LdapName( subentryDn );
599             apDn.remove( apDn.size() - 1 );
600             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );
601             boolean isOldNameSelected = evaluator.evaluate( ss, apDn, oldName, objectClasses );
602             boolean isNewNameSelected = evaluator.evaluate( ss, apDn, newName, objectClasses );
603 
604             if ( isOldNameSelected == isNewNameSelected )
605             {
606                 continue;
607             }
608 
609             // need to remove references to the subentry
610             if ( isOldNameSelected && ! isNewNameSelected )
611             {
612                 for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
613                 {
614                     int op = DirContext.REPLACE_ATTRIBUTE;
615                     Attribute opAttr = entry.get( SUBENTRY_OPATTRS[ii] );
616                     if ( opAttr != null )
617                     {
618                         opAttr = ( Attribute ) opAttr.clone();
619                         opAttr.remove( subentryDn );
620 
621                         if ( opAttr.size() < 1 )
622                         {
623                             op = DirContext.REMOVE_ATTRIBUTE;
624                         }
625 
626                         modList.add( new ModificationItem( op, opAttr ) );
627                     }
628                 }
629             }
630             // need to add references to the subentry
631             else if ( isNewNameSelected && ! isOldNameSelected )
632             {
633                 for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
634                 {
635                     int op = DirContext.REPLACE_ATTRIBUTE;
636                     Attribute opAttr = entry.get( SUBENTRY_OPATTRS[ii] );
637                     if ( opAttr != null )
638                     {
639                         opAttr = ( Attribute ) opAttr.clone();
640                     }
641                     else
642                     {
643                         op = DirContext.ADD_ATTRIBUTE;
644                         opAttr = new LockableAttributeImpl( SUBENTRY_OPATTRS[ii] );
645                     }
646 
647                     opAttr.add( subentryDn );
648                     modList.add( new ModificationItem( op, opAttr ) );
649                 }
650             }
651         }
652 
653         ModificationItem[] mods = new ModificationItem[modList.size()];
654         mods = ( ModificationItem[] ) modList.toArray( mods );
655         return mods;
656     }
657 
658 
659     public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
660     {
661         Attributes entry = nexus.lookup( name );
662         Attribute objectClasses = entry.get( "objectClass" );
663 
664         if ( objectClasses.contains( "subentry" ) )
665         {
666             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( name.toString() );
667             Name apName = ( Name ) name.clone();
668             apName.remove( apName.size() - 1 );
669             Name baseDn = ( Name ) apName.clone();
670             baseDn.addAll( ss.getBase() );
671             Name newName = ( Name ) name.clone();
672             newName.remove( newName.size() - 1 );
673             Name rdn = dnParser.parse( newRn );
674             newName.addAll( rdn );
675 
676             subtrees.put( newName.toString(), ss );
677             next.modifyRn( name, newRn, deleteOldRn );
678 
679             Attributes apAttrs = nexus.lookup( apName );
680             Attribute administrativeRole = apAttrs.get( "administrativeRole" );
681             ExprNode filter = new PresenceNode( "objectclass" );
682             SearchControls controls = new SearchControls();
683             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
684             controls.setReturningAttributes( new String[] { "+", "*" } );
685             NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
686             while ( subentries.hasMore() )
687             {
688                 SearchResult result = ( SearchResult ) subentries.next();
689                 Attributes candidate = result.getAttributes();
690                 Name dn = dnParser.parse( result.getName() );
691 
692                 if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
693                 {
694                     nexus.modify( dn, getOperationalModsForReplace( name, newName, administrativeRole, candidate ) );
695                 }
696             }
697         }
698         else
699         {
700             if ( hasAdministrativeDescendant( name ) )
701             {
702                 String msg = "Will not allow rename operation on entries with administrative descendants.";
703                 log.warn( msg );
704                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
705             }
706             next.modifyRn( name, newRn, deleteOldRn );
707 
708             // calculate the new DN now for use below to modify subentry operational
709             // attributes contained within this regular entry with name changes
710             Name newName = ( Name ) name.clone();
711             newName.remove( newName.size() - 1 );
712             newName.add( newRn );
713             ModificationItem[] mods = getModsOnEntryRdnChange( name, newName, entry );
714 
715             if ( mods.length > 0 )
716             {
717                 nexus.modify( newName, mods );
718             }
719         }
720     }
721 
722 
723     public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn )
724             throws NamingException
725     {
726         Attributes entry = nexus.lookup( oriChildName );
727         Attribute objectClasses = entry.get( "objectClass" );
728 
729         if ( objectClasses.contains( "subentry" ) )
730         {
731             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( oriChildName.toString() );
732             Name apName = ( Name ) oriChildName.clone();
733             apName.remove( apName.size() - 1 );
734             Name baseDn = ( Name ) apName.clone();
735             baseDn.addAll( ss.getBase() );
736             Name newName = ( Name ) newParentName.clone();
737             newName.remove( newName.size() - 1 );
738             Name rdn = dnParser.parse( newRn );
739             newName.addAll( rdn );
740 
741             subtrees.put( newName.toString(), ss );
742             next.move( oriChildName, newParentName, newRn, deleteOldRn );
743 
744             Attributes apAttrs = nexus.lookup( apName );
745             Attribute administrativeRole = apAttrs.get( "administrativeRole" );
746 
747             ExprNode filter = new PresenceNode( "objectclass" );
748             SearchControls controls = new SearchControls();
749             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
750             controls.setReturningAttributes( new String[] { "+", "*" } );
751             NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
752             while ( subentries.hasMore() )
753             {
754                 SearchResult result = ( SearchResult ) subentries.next();
755                 Attributes candidate = result.getAttributes();
756                 Name dn = dnParser.parse( result.getName() );
757 
758                 if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
759                 {
760                     nexus.modify( dn, getOperationalModsForReplace( oriChildName, newName, administrativeRole, candidate ) );
761                 }
762             }
763         }
764         else
765         {
766             if ( hasAdministrativeDescendant( oriChildName ) )
767             {
768                 String msg = "Will not allow rename operation on entries with administrative descendants.";
769                 log.warn( msg );
770                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
771             }
772             next.move( oriChildName, newParentName, newRn, deleteOldRn );
773 
774             // calculate the new DN now for use below to modify subentry operational
775             // attributes contained within this regular entry with name changes
776             Name newName = ( Name ) newParentName.clone();
777             newName.add( newRn );
778             ModificationItem[] mods = getModsOnEntryRdnChange( oriChildName, newName, entry );
779 
780             if ( mods.length > 0 )
781             {
782                 nexus.modify( newName, mods );
783             }
784         }
785     }
786 
787 
788     public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
789     {
790         Attributes entry = nexus.lookup( oriChildName );
791         Attribute objectClasses = entry.get( "objectClass" );
792 
793         if ( objectClasses.contains( "subentry" ) )
794         {
795             SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( oriChildName.toString() );
796             Name apName = ( Name ) oriChildName.clone();
797             apName.remove( apName.size() - 1 );
798             Name baseDn = ( Name ) apName.clone();
799             baseDn.addAll( ss.getBase() );
800             Name newName = ( Name ) newParentName.clone();
801             newName.remove( newName.size() - 1 );
802             newName.add( newParentName.get( newParentName.size() - 1 ) );
803 
804             subtrees.put( newName.toString(), ss );
805             next.move( oriChildName, newParentName );
806 
807             Attributes apAttrs = nexus.lookup( apName );
808             Attribute administrativeRole = apAttrs.get( "administrativeRole" );
809 
810             ExprNode filter = new PresenceNode( "objectclass" );
811             SearchControls controls = new SearchControls();
812             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
813             controls.setReturningAttributes( new String[] { "+", "*" } );
814             NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
815             while ( subentries.hasMore() )
816             {
817                 SearchResult result = ( SearchResult ) subentries.next();
818                 Attributes candidate = result.getAttributes();
819                 Name dn = dnParser.parse( result.getName() );
820 
821                 if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
822                 {
823                     nexus.modify( dn, getOperationalModsForReplace( oriChildName, newName, administrativeRole, candidate ) );
824                 }
825             }
826         }
827         else
828         {
829             if ( hasAdministrativeDescendant( oriChildName ) )
830             {
831                 String msg = "Will not allow rename operation on entries with administrative descendants.";
832                 log.warn( msg );
833                 throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
834             }
835             next.move( oriChildName, newParentName );
836 
837             // calculate the new DN now for use below to modify subentry operational
838             // attributes contained within this regular entry with name changes
839             Name newName = ( Name ) newParentName.clone();
840             newName.add( oriChildName.get( oriChildName.size() - 1 ) );
841             ModificationItem[] mods = getModsOnEntryRdnChange( oriChildName, newName, entry );
842 
843             if ( mods.length > 0 )
844             {
845                 nexus.modify( newName, mods );
846             }
847         }
848     }
849 
850 
851     // -----------------------------------------------------------------------
852     // Methods dealing subentry modification
853     // -----------------------------------------------------------------------
854 
855 
856     public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
857     {
858         Attributes entry = nexus.lookup( name );
859         Attribute objectClasses = entry.get( "objectClass" );
860 
861         if ( objectClasses.contains( "subentry" ) && mods.get( "subtreeSpecification" ) != null )
862         {
863             SubtreeSpecification ssOld = ( SubtreeSpecification ) subtrees.remove( name.toString() );
864             SubtreeSpecification ssNew;
865 
866             try
867             {
868                 ssNew = ssParser.parse( ( String ) mods.get( "subtreeSpecification" ).get() );
869             }
870             catch ( Exception e )
871             {
872                 String msg = "failed to parse the new subtreeSpecification";
873                 log.error( msg, e );
874                 throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
875             }
876 
877             subtrees.put( name.toString(), ssNew );
878             next.modify( name, modOp, mods );
879 
880             // search for all entries selected by the old SS and remove references to subentry
881             Name apName = ( Name ) name.clone();
882             apName.remove( apName.size() - 1 );
883             Name oldBaseDn = ( Name ) apName.clone();
884             oldBaseDn.addAll( ssOld.getBase() );
885             ExprNode filter = new PresenceNode( "objectClass" );
886             SearchControls controls = new SearchControls();
887             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
888             controls.setReturningAttributes( new String[] { "+", "*" } );
889             NamingEnumeration subentries = nexus.search( oldBaseDn, factoryCfg.getEnvironment(), filter, controls );
890             while ( subentries.hasMore() )
891             {
892                 SearchResult result = ( SearchResult ) subentries.next();
893                 Attributes candidate = result.getAttributes();
894                 Name dn = dnParser.parse( result.getName() );
895 
896                 if ( evaluator.evaluate( ssOld, apName, dn, candidate.get( "objectClass" ) ) )
897                 {
898                     nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
899                 }
900             }
901 
902             // search for all selected entries by the new SS and add references to subentry
903             Attributes apAttrs = nexus.lookup( apName );
904             Attribute administrativeRole = apAttrs.get( "administrativeRole" );
905             Attributes operational = getSubentryOperatationalAttributes( name, administrativeRole );
906             Name newBaseDn = ( Name ) apName.clone();
907             newBaseDn.addAll( ssNew.getBase() );
908             subentries = nexus.search( newBaseDn, factoryCfg.getEnvironment(), filter, controls );
909             while ( subentries.hasMore() )
910             {
911                 SearchResult result = ( SearchResult ) subentries.next();
912                 Attributes candidate = result.getAttributes();
913                 Name dn = dnParser.parse( result.getName() );
914 
915                 if ( evaluator.evaluate( ssNew, apName, dn, candidate.get( "objectClass" ) ) )
916                 {
917                     nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
918                 }
919             }
920         }
921         else
922         {
923             next.modify( name, modOp, mods );
924         }
925     }
926 
927 
928     public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
929     {
930         Attributes entry = nexus.lookup( name );
931         Attribute objectClasses = entry.get( "objectClass" );
932         boolean isSubtreeSpecificationModification = false;
933         ModificationItem subtreeMod = null;
934 
935         for ( int ii = 0; ii < mods.length; ii++ )
936         {
937             if ( "subtreeSpecification".equalsIgnoreCase( mods[ii].getAttribute().getID() ) )
938             {
939                 isSubtreeSpecificationModification = true;
940                 subtreeMod = mods[ii];
941             }
942         }
943 
944         if ( objectClasses.contains( "subentry" ) && isSubtreeSpecificationModification )
945         {
946             SubtreeSpecification ssOld = ( SubtreeSpecification ) subtrees.remove( name.toString() );
947             SubtreeSpecification ssNew;
948 
949             try
950             {
951                 ssNew = ssParser.parse( ( String ) subtreeMod.getAttribute().get() );
952             }
953             catch ( Exception e )
954             {
955                 String msg = "failed to parse the new subtreeSpecification";
956                 log.error( msg, e );
957                 throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
958             }
959 
960             subtrees.put( name.toString(), ssNew );
961             next.modify( name, mods );
962 
963             // search for all entries selected by the old SS and remove references to subentry
964             Name apName = ( Name ) name.clone();
965             apName.remove( apName.size() - 1 );
966             Name oldBaseDn = ( Name ) apName.clone();
967             oldBaseDn.addAll( ssOld.getBase() );
968             ExprNode filter = new PresenceNode( "objectClass" );
969             SearchControls controls = new SearchControls();
970             controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
971             controls.setReturningAttributes( new String[] { "+", "*" } );
972             NamingEnumeration subentries = nexus.search( oldBaseDn, factoryCfg.getEnvironment(), filter, controls );
973             while ( subentries.hasMore() )
974             {
975                 SearchResult result = ( SearchResult ) subentries.next();
976                 Attributes candidate = result.getAttributes();
977                 Name dn = dnParser.parse( result.getName() );
978 
979                 if ( evaluator.evaluate( ssOld, apName, dn, candidate.get( "objectClass" ) ) )
980                 {
981                     nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
982                 }
983             }
984 
985             // search for all selected entries by the new SS and add references to subentry
986             Attributes apAttrs = nexus.lookup( apName );
987             Attribute administrativeRole = apAttrs.get( "administrativeRole" );
988             Attributes operational = getSubentryOperatationalAttributes( name, administrativeRole );
989             Name newBaseDn = ( Name ) apName.clone();
990             newBaseDn.addAll( ssNew.getBase() );
991             subentries = nexus.search( newBaseDn, factoryCfg.getEnvironment(), filter, controls );
992             while ( subentries.hasMore() )
993             {
994                 SearchResult result = ( SearchResult ) subentries.next();
995                 Attributes candidate = result.getAttributes();
996                 Name dn = dnParser.parse( result.getName() );
997 
998                 if ( evaluator.evaluate( ssNew, apName, dn, candidate.get( "objectClass" ) ) )
999                 {
1000                     nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
1001                 }
1002             }
1003         }
1004         else
1005         {
1006             next.modify( name, mods );
1007         }
1008     }
1009 
1010 
1011     // -----------------------------------------------------------------------
1012     // Utility Methods
1013     // -----------------------------------------------------------------------
1014 
1015 
1016     private ModificationItem[] getOperationalModsForReplace( Name oldName, Name newName, Attribute administrativeRole,
1017                                                              Attributes entry ) throws NamingException
1018     {
1019         List modList = new ArrayList();
1020         NamingEnumeration roles = administrativeRole.getAll();
1021         while ( roles.hasMore() )
1022         {
1023             Attribute operational;
1024             String role = ( String ) roles.next();
1025 
1026             if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
1027             {
1028                 operational = ( Attribute ) entry.get( AUTONOUMOUS_AREA_SUBENTRY ).clone();
1029                 if ( operational == null )
1030                 {
1031                     operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
1032                     operational.add( newName.toString() );
1033                 }
1034                 else
1035                 {
1036                     operational.remove( oldName.toString() );
1037                     operational.add( newName.toString() );
1038                 }
1039             }
1040             else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
1041             {
1042                 operational = ( Attribute ) entry.get( AC_SUBENTRY ).clone();
1043                 if ( operational == null )
1044                 {
1045                     operational = new LockableAttributeImpl( AC_SUBENTRY );
1046                     operational.add( newName.toString() );
1047                 }
1048                 else
1049                 {
1050                     operational.remove( oldName.toString() );
1051                     operational.add( newName.toString() );
1052                 }
1053             }
1054             else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
1055             {
1056                 operational = ( Attribute ) entry.get( SCHEMA_AREA_SUBENTRY ).clone();
1057                 if ( operational == null )
1058                 {
1059                     operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
1060                     operational.add( newName.toString() );
1061                 }
1062                 else
1063                 {
1064                     operational.remove( oldName.toString() );
1065                     operational.add( newName.toString() );
1066                 }
1067             }
1068             else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
1069                       role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
1070             {
1071                 operational = ( Attribute ) entry.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ).clone();
1072                 if ( operational == null )
1073                 {
1074                     operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
1075                     operational.add( newName.toString() );
1076                 }
1077                 else
1078                 {
1079                     operational.remove( oldName.toString() );
1080                     operational.add( newName.toString() );
1081                 }
1082             }
1083             else
1084             {
1085                 throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
1086                         + role + "' in administrative point of subentry " + oldName + ". The values of this attribute"
1087                         + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
1088                         + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
1089                         + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
1090             }
1091 
1092             modList.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, operational ) );
1093         }
1094 
1095         ModificationItem[] mods = new ModificationItem[modList.size()];
1096         return ( ModificationItem[] ) modList.toArray( mods );
1097     }
1098 
1099 
1100     /***
1101      * Gets the subschema operational attributes to be added to or removed from
1102      * an entry selected by a subentry's subtreeSpecification.
1103      *
1104      * @param name the normalized distinguished name of the subentry (the value of op attrs)
1105      * @param administrativeRole the roles the administrative point participates in
1106      * @return the set of attributes to be added or removed from entries
1107      * @throws NamingException if there are problems accessing attributes
1108      */
1109     private Attributes getSubentryOperatationalAttributes( Name name, Attribute administrativeRole )
1110             throws NamingException
1111     {
1112         Attributes operational = new LockableAttributesImpl();
1113         NamingEnumeration roles = administrativeRole.getAll();
1114         while ( roles.hasMore() )
1115         {
1116             String role = ( String ) roles.next();
1117 
1118             if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
1119             {
1120                 if ( operational.get( AUTONOUMOUS_AREA_SUBENTRY ) == null )
1121                 {
1122                     operational.put( AUTONOUMOUS_AREA_SUBENTRY, name.toString() );
1123                 }
1124                 else
1125                 {
1126                     operational.get( AUTONOUMOUS_AREA_SUBENTRY ).add( name.toString() );
1127                 }
1128             }
1129             else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
1130             {
1131                 if ( operational.get( AC_SUBENTRY ) == null )
1132                 {
1133                     operational.put( AC_SUBENTRY, name.toString() );
1134                 }
1135                 else
1136                 {
1137                     operational.get( AC_SUBENTRY ).add( name.toString() );
1138                 }
1139             }
1140             else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
1141             {
1142                 if ( operational.get( SCHEMA_AREA_SUBENTRY ) == null )
1143                 {
1144                     operational.put( SCHEMA_AREA_SUBENTRY, name.toString() );
1145                 }
1146                 else
1147                 {
1148                     operational.get( SCHEMA_AREA_SUBENTRY ).add( name.toString() );
1149                 }
1150             }
1151             else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
1152                       role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
1153             {
1154                 if ( operational.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ) == null )
1155                 {
1156                     operational.put( COLLECTIVE_ATTRIBUTE_SUBENTRIES, name.toString() );
1157                 }
1158                 else
1159                 {
1160                     operational.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ).add( name.toString() );
1161                 }
1162             }
1163             else
1164             {
1165                 throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
1166                         + role + "' in administrative point of subentry " + name + ". The values of this attribute are"
1167                         + " constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
1168                         + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
1169                         + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
1170             }
1171         }
1172 
1173         return operational;
1174     }
1175 
1176 
1177     /***
1178      * Calculates the subentry operational attributes to remove from a candidate
1179      * entry selected by a subtreeSpecification.  When we remove a subentry we
1180      * must remove the operational attributes in the entries that were once selected
1181      * by the subtree specification of that subentry.  To do so we must perform
1182      * a modify operation with the set of modifications to perform.  This method
1183      * calculates those modifications.
1184      *
1185      * @param subentryDn the distinguished name of the subentry
1186      * @param candidate the candidate entry to removed from the
1187      * @return the set of modifications required to remove an entry's reference to
1188      * a subentry
1189      */
1190     private ModificationItem[] getOperationalModsForRemove( Name subentryDn, Attributes candidate )
1191     {
1192         List modList = new ArrayList();
1193         String dn = subentryDn.toString();
1194 
1195         for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
1196         {
1197             String opAttrId = SUBENTRY_OPATTRS[ii];
1198             Attribute opAttr = candidate.get( opAttrId );
1199 
1200             if ( opAttr != null && opAttr.contains( dn ) )
1201             {
1202                 Attribute attr = new LockableAttributeImpl( SUBENTRY_OPATTRS[ii] );
1203                 attr.add( dn );
1204                 modList.add( new ModificationItem( DirContext.REMOVE_ATTRIBUTE, attr ) );
1205             }
1206         }
1207 
1208         ModificationItem[] mods = new ModificationItem[modList.size()];
1209         return ( ModificationItem[] ) modList.toArray( mods );
1210     }
1211 
1212 
1213     /***
1214      * Calculates the subentry operational attributes to add or replace from
1215      * a candidate entry selected by a subtree specification.  When a subentry
1216      * is added or it's specification is modified some entries must have new
1217      * operational attributes added to it to point back to the associated
1218      * subentry.  To do so a modify operation must be performed on entries
1219      * selected by the subtree specification.  This method calculates the
1220      * modify operation to be performed on the entry.
1221      *
1222      * @param entry the entry being modified
1223      * @param operational the set of operational attributes supported by the AP
1224      * of the subentry
1225      * @return the set of modifications needed to update the entry
1226      */
1227     public ModificationItem[] getOperationalModsForAdd( Attributes entry, Attributes operational ) throws NamingException
1228     {
1229         List modList = new ArrayList();
1230 
1231         NamingEnumeration opAttrIds = operational.getIDs();
1232         while ( opAttrIds.hasMore() )
1233         {
1234             int op = DirContext.REPLACE_ATTRIBUTE;
1235             String opAttrId = ( String ) opAttrIds.next();
1236             Attribute result = new LockableAttributeImpl( opAttrId );
1237             Attribute opAttrAdditions = operational.get( opAttrId );
1238             Attribute opAttrInEntry = entry.get( opAttrId );
1239 
1240             for ( int ii = 0; ii < opAttrAdditions.size(); ii++ )
1241             {
1242                 result.add( opAttrAdditions.get( ii ) );
1243             }
1244 
1245             if ( opAttrInEntry != null && opAttrInEntry.size() > 0 )
1246             {
1247                 for ( int ii = 0; ii < opAttrInEntry.size(); ii++ )
1248                 {
1249                     result.add( opAttrInEntry.get( ii ) );
1250                 }
1251             }
1252             else
1253             {
1254                 op = DirContext.ADD_ATTRIBUTE;
1255             }
1256 
1257             modList.add( new ModificationItem( op, result ) );
1258         }
1259 
1260         ModificationItem[] mods = new ModificationItem[modList.size()];
1261         mods = ( ModificationItem[] ) modList.toArray( mods );
1262         return mods;
1263     }
1264 }