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.jndi;
18  
19  
20  import java.io.Serializable;
21  import java.util.HashSet;
22  import java.util.Hashtable;
23  import java.util.Iterator;
24  import java.util.Set;
25  
26  import javax.naming.ConfigurationException;
27  import javax.naming.Context;
28  import javax.naming.InvalidNameException;
29  import javax.naming.Name;
30  import javax.naming.NameNotFoundException;
31  import javax.naming.NameParser;
32  import javax.naming.NamingEnumeration;
33  import javax.naming.NamingException;
34  import javax.naming.Reference;
35  import javax.naming.Referenceable;
36  import javax.naming.directory.Attribute;
37  import javax.naming.directory.Attributes;
38  import javax.naming.directory.DirContext;
39  import javax.naming.directory.SearchControls;
40  import javax.naming.event.EventContext;
41  import javax.naming.event.NamingListener;
42  import javax.naming.ldap.Control;
43  import javax.naming.spi.DirStateFactory;
44  import javax.naming.spi.DirectoryManager;
45  
46  import org.apache.ldap.common.exception.LdapNoPermissionException;
47  import org.apache.ldap.common.filter.ExprNode;
48  import org.apache.ldap.common.filter.PresenceNode;
49  import org.apache.ldap.common.message.LockableAttributesImpl;
50  import org.apache.ldap.common.name.LdapName;
51  import org.apache.ldap.common.util.NamespaceTools;
52  import org.apache.ldap.server.DirectoryServiceConfiguration;
53  import org.apache.ldap.server.DirectoryService;
54  import org.apache.ldap.server.authn.AuthenticationService;
55  import org.apache.ldap.server.authn.LdapPrincipal;
56  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
57  import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy;
58  
59  
60  /***
61   * A non-federated abstract Context implementation.
62   *
63   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64   * @version $Rev: 328280 $
65   */
66  public abstract class ServerContext implements EventContext
67  {
68      /*** property key used for deleting the old RDN on a rename */
69      public static final String DELETE_OLD_RDN_PROP = "java.naming.ldap.deleteRDN";
70  
71      /*** The interceptor proxy to the backend nexus */
72      private final DirectoryPartitionNexus nexusProxy;
73  
74      /*** The cloned environment used by this Context */
75      private final Hashtable env;
76  
77      /*** The distinguished name of this Context */
78      private final LdapName dn;
79  
80      /*** The set of registered NamingListeners */
81      private final Set listeners = new HashSet();
82  
83      /*** The Principal associated with this context */
84      private LdapPrincipal principal;
85  
86      // ------------------------------------------------------------------------
87      // Constructors
88      // ------------------------------------------------------------------------
89  
90  
91      /***
92       * Must be called by all subclasses to initialize the nexus proxy and the
93       * environment settings to be used by this Context implementation.  This
94       * specific contstructor relies on the presence of the {@link
95       * Context#PROVIDER_URL} key and value to determine the distinguished name
96       * of the newly created context.  It also checks to make sure the
97       * referenced name actually exists within the system.  This constructor
98       * is used for all InitialContext requests.
99       * 
100      * @param service the parent service that manages this context
101      * @param env the environment properties used by this context.
102      * @throws NamingException if the environment parameters are not set 
103      * correctly.
104      */
105     protected ServerContext( DirectoryService service, Hashtable env ) throws NamingException
106     {
107         // set references to cloned env and the proxy
108         this.nexusProxy = new DirectoryPartitionNexusProxy( this, service );
109         
110         DirectoryServiceConfiguration cfg = service.getConfiguration();
111         
112         this.env = ( Hashtable ) cfg.getEnvironment().clone();
113         this.env.putAll( env );
114 
115         /* --------------------------------------------------------------------
116          * check for the provider URL property and make sure it exists
117          * as a valid value or else we need to throw a configuration error
118          * ------------------------------------------------------------------ */
119         if ( ! env.containsKey( Context.PROVIDER_URL ) )
120         {
121             String msg = "Expected property " + Context.PROVIDER_URL;
122             msg += " but could not find it in env!";
123 
124             throw new ConfigurationException( msg );
125         }
126 
127         String url = ( String ) env.get( Context.PROVIDER_URL );
128 
129         if ( url == null )
130         {
131             String msg = "Expected value for property " + Context.PROVIDER_URL;
132             msg += " but it was set to null in env!";
133 
134             throw new ConfigurationException( msg );
135         }
136 
137         dn = new LdapName( url );
138 
139         if ( ! nexusProxy.hasEntry( dn ) )
140         {
141             throw new NameNotFoundException( dn + " does not exist" );
142         }
143     }
144 
145 
146     /***
147      * Must be called by all subclasses to initialize the nexus proxy and the
148      * environment settings to be used by this Context implementation.  This
149      * constructor is used to propagate new contexts from existing contexts.
150      *
151      * @param principal the directory user principal that is propagated
152      * @param nexusProxy the intercepting proxy to the nexus
153      * @param env the environment properties used by this context
154      * @param dn the distinguished name of this context
155      */
156     protected ServerContext( LdapPrincipal principal, DirectoryPartitionNexus nexusProxy, Hashtable env, Name dn )
157     {
158         this.dn = ( LdapName ) dn.clone();
159 
160         this.env = ( Hashtable ) env.clone();
161 
162         this.env.put( PROVIDER_URL, dn.toString() );
163 
164         this.nexusProxy = nexusProxy;
165 
166         this.principal = principal;
167     }
168 
169 
170     // ------------------------------------------------------------------------
171     // New Impl Specific Public Methods
172     // ------------------------------------------------------------------------
173 
174 
175     /***
176      * Gets the principal of the authenticated user which also happens to own
177      */
178     public LdapPrincipal getPrincipal()
179     {
180         return principal;
181     }
182 
183 
184     /***
185      * Sets the principal of the authenticated user which also happens to own.
186      * This method can be invoked only once to keep this property safe.  This
187      * method has been changed to be public but it can only be set by the
188      * AuthenticationService to prevent malicious code from changing the
189      * effective principal.
190      */
191     public void setPrincipal( AuthenticationService.TrustedPrincipalWrapper wrapper )
192     {
193         this.principal = wrapper.getPrincipal();
194     }
195 
196 
197     // ------------------------------------------------------------------------
198     // Protected Accessor Methods
199     // ------------------------------------------------------------------------
200 
201 
202     /***
203      * Gets the RootNexus proxy.
204      *
205      * @return the proxy to the backend nexus.
206      */
207     protected DirectoryPartitionNexus getNexusProxy()
208     {
209        return nexusProxy ;
210     }
211 
212 
213     /***
214      * Gets the distinguished name of the entry associated with this Context.
215      * 
216      * @return the distinguished name of this Context's entry.
217      */
218     protected Name getDn()
219     {
220         return dn;
221     }
222 
223 
224     // ------------------------------------------------------------------------
225     // JNDI Context Interface Methods
226     // ------------------------------------------------------------------------
227 
228 
229     /***
230      * @see javax.naming.Context#close()
231      */
232     public void close() throws NamingException
233     {
234         Iterator list = listeners.iterator();
235         while ( list.hasNext() )
236         {
237             ( ( DirectoryPartitionNexusProxy ) this.nexusProxy )
238                     .removeNamingListener( this, ( NamingListener ) list.next() );
239         }
240     }
241 
242 
243     /***
244      * @see javax.naming.Context#getNameInNamespace()
245      */
246     public String getNameInNamespace() throws NamingException
247     {
248         return dn.toString();
249     }
250 
251 
252     /***
253      * @see javax.naming.Context#getEnvironment()
254      */
255     public Hashtable getEnvironment()
256     {
257         return env;
258     }
259 
260 
261     /***
262      * @see javax.naming.Context#addToEnvironment(java.lang.String, 
263      * java.lang.Object)
264      */
265     public Object addToEnvironment( String propName, Object propVal ) throws NamingException
266     {
267         return env.put( propName, propVal );
268     }
269 
270 
271     /***
272      * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
273      */
274     public Object removeFromEnvironment( String propName ) throws NamingException
275     {
276         return env.remove( propName );
277     }
278 
279 
280     /***
281      * @see javax.naming.Context#createSubcontext(java.lang.String)
282      */
283     public Context createSubcontext( String name ) throws NamingException
284     {
285         return createSubcontext( new LdapName( name ) );
286     }
287 
288 
289     /***
290      * @see javax.naming.Context#createSubcontext(javax.naming.Name)
291      */
292     public Context createSubcontext( Name name ) throws NamingException
293     {
294         Attributes attributes = new LockableAttributesImpl();
295 
296         LdapName target = buildTarget( name );
297 
298         String rdn = name.get( name.size() - 1 );
299 
300         String rdnAttribute = NamespaceTools.getRdnAttribute( rdn );
301 
302         String rdnValue = NamespaceTools.getRdnValue( rdn );
303 
304         attributes.put( rdnAttribute, rdnValue );
305 
306         attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.JCONTAINER_ATTR );
307 
308         attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.TOP_ATTR );
309         
310         /*
311          * Add the new context to the server which as a side effect adds 
312          * operational attributes to the attributes refering instance which
313          * can them be used to initialize a new ServerLdapContext.  Remember
314          * we need to copy over the controls as well to propagate the complete 
315          * environment besides whats in the hashtable for env.
316          */
317         nexusProxy.add( target.toString(), target, attributes );
318         
319         ServerLdapContext ctx = new ServerLdapContext( principal, nexusProxy, env, target );
320 
321         Control [] controls = ( Control [] ) ( ( ServerLdapContext ) this ).getRequestControls().clone();
322 
323         ctx.setRequestControls( controls );
324 
325         return ctx;
326     }
327 
328 
329     /***
330      * @see javax.naming.Context#destroySubcontext(java.lang.String)
331      */
332     public void destroySubcontext( String name ) throws NamingException
333     {
334         destroySubcontext( new LdapName( name ) );
335     }
336 
337 
338     /***
339      * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
340      */
341     public void destroySubcontext( Name name ) throws NamingException
342     {
343         Name target = buildTarget( name );
344 
345         if ( target.size() == 0 )
346         {
347             throw new LdapNoPermissionException( "can't delete the rootDSE" );
348         }
349 
350         nexusProxy.delete( target );
351     }
352 
353 
354     /***
355      * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
356      */
357     public void bind( String name, Object obj ) throws NamingException
358     {
359         bind( new LdapName( name ), obj );
360     }
361     
362 
363     /***
364      * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
365      */
366     public void bind( Name name, Object obj ) throws NamingException
367     {
368         // First, use state factories to do a transformation
369         DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );
370 
371         Attributes outAttrs = res.getAttributes();
372 
373         if ( outAttrs != null )
374         {
375             Name target = buildTarget( name );
376 
377             nexusProxy.add( target.toString(), target, outAttrs );
378 
379             return;
380         }
381 
382         // Check for Referenceable
383         if ( obj instanceof Referenceable )
384         {
385             throw new NamingException( "Do not know how to store Referenceables yet!" );
386         }
387 
388         // Store different formats
389         if ( obj instanceof Reference )
390         {
391             // Store as ref and add outAttrs
392 
393             throw new NamingException( "Do not know how to store References yet!" );
394         }
395         else if ( obj instanceof Serializable )
396         {
397             // Serialize and add outAttrs
398 
399             Attributes attributes = new LockableAttributesImpl();
400 
401             if ( outAttrs != null && outAttrs.size() > 0 )
402             {
403                 NamingEnumeration list = outAttrs.getAll();
404 
405                 while ( list.hasMore() )
406                 {
407                     attributes.put( ( Attribute ) list.next() );
408                 }
409             }
410 
411             Name target = buildTarget( name );
412 
413             // Serialize object into entry attributes and add it.
414 
415             JavaLdapSupport.serialize( attributes, obj );
416 
417             nexusProxy.add( target.toString(), target, attributes );
418         }
419         else if ( obj instanceof DirContext )
420         {
421             // Grab attributes and merge with outAttrs
422 
423             Attributes attributes = ( ( DirContext ) obj ).getAttributes( "" );
424 
425             if ( outAttrs != null && outAttrs.size() > 0 )
426             {
427                 NamingEnumeration list = outAttrs.getAll();
428 
429                 while ( list.hasMore() )
430                 {
431                     attributes.put( ( Attribute ) list.next() );
432                 }
433             }
434 
435             Name target = buildTarget( name );
436 
437             nexusProxy.add( target.toString(), target, attributes );
438         }
439         else
440         {
441             throw new NamingException( "Can't find a way to bind: " + obj );
442         }
443     }
444 
445 
446     /***
447      * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
448      */
449     public void rename( String oldName, String newName ) throws NamingException
450     {
451         rename( new LdapName( oldName ), new LdapName( newName ) );
452     }
453 
454 
455     /***
456      * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
457      */
458     public void rename( Name oldName, Name newName ) throws NamingException
459     {
460         Name oldDn = buildTarget( oldName );
461 
462         Name newDn = buildTarget( newName );
463 
464         if ( oldDn.size() == 0 )
465         {
466             throw new LdapNoPermissionException( "can't rename the rootDSE" );
467         }
468 
469         Name oldBase = oldName.getPrefix( 1 );
470 
471         Name newBase = newName.getPrefix( 1 );
472 
473         String newRdn = newName.get( newName.size() - 1 );
474 
475         String oldRdn = oldName.get( oldName.size() - 1 );
476                 
477         boolean delOldRdn = true;
478             
479         /*
480          * Attempt to use the java.naming.ldap.deleteRDN environment property
481          * to get an override for the deleteOldRdn option to modifyRdn.  
482          */
483         if ( null != env.get( DELETE_OLD_RDN_PROP ) )
484         {
485             String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
486 
487             delOldRdn = ! delOldRdnStr.equals( "false" );
488 
489             delOldRdn = delOldRdn || delOldRdnStr.equals( "no" );
490 
491             delOldRdn = delOldRdn || delOldRdnStr.equals( "0" );
492         }
493 
494         /*
495          * We need to determine if this rename operation corresponds to a simple
496          * RDN name change or a move operation.  If the two names are the same
497          * except for the RDN then it is a simple modifyRdn operation.  If the
498          * names differ in size or have a different baseDN then the operation is
499          * a move operation.  Furthermore if the RDN in the move operation 
500          * changes it is both an RDN change and a move operation.
501          */
502         if ( oldName.size() == newName.size() && oldBase.equals( newBase ) )
503         {
504             nexusProxy.modifyRn( oldDn, newRdn, delOldRdn );
505         }
506         else
507         {
508             Name parent = newDn.getPrefix( 1 );
509             
510             if ( newRdn.equalsIgnoreCase( oldRdn ) )
511             {
512                 nexusProxy.move( oldDn, parent );
513             }
514             else
515             {
516                 nexusProxy.move( oldDn, parent, newRdn, delOldRdn );
517             }
518         }
519     }
520 
521 
522     /***
523      * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
524      */
525     public void rebind( String name, Object obj ) throws NamingException
526     {
527         rebind( new LdapName( name ), obj );
528     }
529 
530 
531     /***
532      * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
533      */
534     public void rebind( Name name, Object obj ) throws NamingException
535     {
536         Name target = buildTarget( name );
537 
538         if ( nexusProxy.hasEntry( target ) )
539         {
540             nexusProxy.delete( target );
541         }
542 
543         bind( name, obj );
544     }
545 
546 
547     /***
548      * @see javax.naming.Context#unbind(java.lang.String)
549      */
550     public void unbind( String name ) throws NamingException
551     {
552         unbind( new LdapName( name ) );
553     }
554 
555 
556     /***
557      * @see javax.naming.Context#unbind(javax.naming.Name)
558      */
559     public void unbind( Name name ) throws NamingException
560     {
561         nexusProxy.delete( buildTarget( name ) );
562     }
563 
564 
565     /***
566      * @see javax.naming.Context#lookup(java.lang.String)
567      */
568     public Object lookup( String name ) throws NamingException
569     {
570         return lookup( new LdapName( name ) );
571     }
572 
573 
574     /***
575      * @see javax.naming.Context#lookup(javax.naming.Name)
576      */
577     public Object lookup( Name name ) throws NamingException
578     {
579         Object obj;
580         LdapName target = buildTarget( name );
581         Attributes attributes = nexusProxy.lookup( target );
582 
583         try
584         {
585             obj = DirectoryManager.getObjectInstance( null, name, this, env, attributes );
586         }
587         catch ( Exception e )
588         {
589             String msg = "Failed to create an object for " + target;
590             msg += " using object factories within the context's environment.";
591             NamingException ne = new NamingException( msg );
592             ne.setRootCause( e );
593             throw ne;
594         }
595 
596         if ( obj != null )
597         {
598             return obj;
599         }
600 
601         // First lets test and see if the entry is a serialized java object
602         if ( attributes.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
603         {
604             // Give back serialized object and not a context
605             return JavaLdapSupport.deserialize( attributes );
606         }
607         
608         // Initialize and return a context since the entry is not a java object
609         ServerLdapContext ctx = new ServerLdapContext( principal, nexusProxy, env, target );
610             
611         // Need to add controls to propagate extended ldap operational env
612         Control [] controls = ( ( ServerLdapContext ) this ).getRequestControls();
613 
614         if ( null != controls )
615         {    
616             ctx.setRequestControls( ( Control [] ) controls.clone() );
617         }
618         
619         return ctx;
620     }
621 
622 
623     /***
624      * @see javax.naming.Context#lookupLink(java.lang.String)
625      */
626     public Object lookupLink( String name ) throws NamingException
627     {
628         throw new UnsupportedOperationException();
629     }
630 
631 
632     /***
633      * @see javax.naming.Context#lookupLink(javax.naming.Name)
634      */
635     public Object lookupLink( Name name ) throws NamingException
636     {
637         throw new UnsupportedOperationException();
638     }
639 
640 
641     /***
642      * Non-federated implementation presuming the name argument is not a 
643      * composite name spanning multiple namespaces but a compound name in 
644      * the same LDAP namespace.  Hence the parser returned is always the
645      * same as calling this method with the empty String.
646      * 
647      * @see javax.naming.Context#getNameParser(java.lang.String)
648      */
649     public NameParser getNameParser( String name ) throws NamingException
650     {
651         return LdapName.getNameParser();
652     }
653 
654 
655     /***
656      * Non-federated implementation presuming the name argument is not a 
657      * composite name spanning multiple namespaces but a compound name in 
658      * the same LDAP namespace.  Hence the parser returned is always the
659      * same as calling this method with the empty String Name.
660      * 
661      * @see javax.naming.Context#getNameParser(javax.naming.Name)
662      */
663     public NameParser getNameParser( Name name ) throws NamingException
664     {
665         return LdapName.getNameParser();
666     }
667 
668 
669     /***
670      * @see javax.naming.Context#list(java.lang.String)
671      */
672     public NamingEnumeration list( String name ) throws NamingException
673     {
674         return list( new LdapName( name ) );
675     }
676 
677 
678     /***
679      * @see javax.naming.Context#list(javax.naming.Name)
680      */
681     public NamingEnumeration list( Name name ) throws NamingException
682     {
683         return nexusProxy.list( buildTarget( name ) );
684     }
685 
686 
687     /***
688      * @see javax.naming.Context#listBindings(java.lang.String)
689      */
690     public NamingEnumeration listBindings( String name ) throws NamingException
691     {
692         return listBindings( new LdapName( name ) );
693     }
694 
695 
696     /***
697      * @see javax.naming.Context#listBindings(javax.naming.Name)
698      */
699     public NamingEnumeration listBindings( Name name ) throws NamingException
700     {
701         // Conduct a special one level search at base for all objects
702         Name base = buildTarget( name );
703 
704         PresenceNode filter = new PresenceNode( "objectClass" );
705 
706         SearchControls ctls = new SearchControls();
707 
708         ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
709 
710         return nexusProxy.search( base , getEnvironment(), filter, ctls );
711     }
712 
713 
714     /***
715      * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
716      */
717     public String composeName( String name, String prefix ) throws NamingException
718     {
719         return composeName( new LdapName( name ), new LdapName( prefix ) ).toString();
720     }
721 
722 
723     /***
724      * @see javax.naming.Context#composeName(javax.naming.Name,
725      * javax.naming.Name)
726      */
727     public Name composeName( Name name, Name prefix ) throws NamingException
728     {
729         // No prefix reduces to name, or the name relative to this context
730         if ( prefix == null || prefix.size() == 0 )
731         {
732             return name;
733         }
734 
735         /*
736          * Example: This context is ou=people and say name is the relative
737          * name of uid=jwalker and the prefix is dc=domain.  Then we must
738          * compose the name relative to prefix which would be:
739          * 
740          * uid=jwalker,ou=people,dc=domain.
741          * 
742          * The following general algorithm generates the right name:
743          *      1). Find the Dn for name and walk it from the head to tail
744          *          trying to match for the head of prefix.
745          *      2). Remove name components from the Dn until a match for the 
746          *          head of the prefix is found.
747          *      3). Return the remainder of the fqn or Dn after chewing off some
748          */
749          
750         // 1). Find the Dn for name and walk it from the head to tail
751         Name fqn = buildTarget( name );
752 
753         String head = prefix.get( 0 );
754         
755         // 2). Walk the fqn trying to match for the head of the prefix
756         while ( fqn.size() > 0 )
757         {
758             // match found end loop
759             if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
760             {
761                 return fqn;
762             }
763             else // 2). Remove name components from the Dn until a match 
764             {
765                 fqn.remove( 0 );
766             }
767         }
768 
769         String msg = "The prefix '" + prefix + "' is not an ancestor of this ";
770 
771         msg += "entry '" + dn + "'";
772 
773         throw new NamingException( msg );
774     }
775 
776 
777     // ------------------------------------------------------------------------
778     // EventContext implementations
779     // ------------------------------------------------------------------------
780 
781 
782     public void addNamingListener( Name name, int scope, NamingListener namingListener ) throws NamingException
783     {
784         ExprNode filter = new PresenceNode( "objectClass" );
785         SearchControls controls = new SearchControls();
786         controls.setSearchScope( scope );
787         ( ( DirectoryPartitionNexusProxy ) this.nexusProxy )
788                 .addNamingListener( this, buildTarget( name ), filter, controls, namingListener );
789         listeners.add( namingListener );
790     }
791 
792 
793     public void addNamingListener( String name, int scope, NamingListener namingListener ) throws NamingException
794     {
795         addNamingListener( new LdapName( name ), scope, namingListener );
796     }
797 
798 
799     public void removeNamingListener( NamingListener namingListener ) throws NamingException
800     {
801         ( ( DirectoryPartitionNexusProxy ) this.nexusProxy ).removeNamingListener( this, namingListener );
802         listeners.remove( namingListener );
803     }
804 
805 
806     public boolean targetMustExist() throws NamingException
807     {
808         return false;
809     }
810 
811 
812     /***
813      * Allows subclasses to register and unregister listeners.
814      *
815      * @return the set of listeners used for tracking registered name listeners.
816      */
817     protected Set getListeners()
818     {
819         return listeners;
820     }
821 
822 
823     // ------------------------------------------------------------------------
824     // Utility Methods to Reduce Code
825     // ------------------------------------------------------------------------
826     
827     
828     /***
829      * Clones this context's DN and adds the components of the name relative to 
830      * this context to the left hand side of this context's cloned DN. 
831      * 
832      * @param relativeName a name relative to this context.
833      * @return the name of the target
834      * @throws InvalidNameException if relativeName is not a valid name in
835      *      the LDAP namespace.
836      */
837     LdapName buildTarget( Name relativeName ) throws InvalidNameException
838     {
839         // Clone our DN or absolute path
840         LdapName target = ( LdapName ) dn.clone();
841         
842         // Add to left hand side of cloned DN the relative name arg
843         target.addAll( target.size(), relativeName );
844 
845         return target;
846     }
847 }