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.jndi;
021    
022    
023    import java.io.Serializable;
024    import java.util.HashMap;
025    import java.util.Hashtable;
026    import java.util.List;
027    import java.util.Map;
028    
029    import javax.naming.Context;
030    import javax.naming.InvalidNameException;
031    import javax.naming.Name;
032    import javax.naming.NameNotFoundException;
033    import javax.naming.NameParser;
034    import javax.naming.NamingEnumeration;
035    import javax.naming.NamingException;
036    import javax.naming.NoPermissionException;
037    import javax.naming.Reference;
038    import javax.naming.Referenceable;
039    import javax.naming.directory.DirContext;
040    import javax.naming.directory.SchemaViolationException;
041    import javax.naming.directory.SearchControls;
042    import javax.naming.event.EventContext;
043    import javax.naming.event.NamingListener;
044    import javax.naming.ldap.Control;
045    import javax.naming.ldap.LdapName;
046    import javax.naming.spi.DirStateFactory;
047    import javax.naming.spi.DirectoryManager;
048    
049    import org.apache.directory.server.core.CoreSession;
050    import org.apache.directory.server.core.DefaultCoreSession;
051    import org.apache.directory.server.core.DirectoryService;
052    import org.apache.directory.server.core.LdapPrincipal;
053    import org.apache.directory.server.core.OperationManager;
054    import org.apache.directory.server.core.entry.ClonedServerEntry;
055    import org.apache.directory.server.core.entry.ServerEntryUtils;
056    import org.apache.directory.server.core.event.DirectoryListener;
057    import org.apache.directory.server.core.event.NotificationCriteria;
058    import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
059    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
060    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
061    import org.apache.directory.server.core.interceptor.context.BindOperationContext;
062    import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
063    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
064    import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
065    import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
066    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
067    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
068    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
069    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
070    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
071    import org.apache.directory.server.core.interceptor.context.OperationContext;
072    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
073    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
074    import org.apache.directory.server.i18n.I18n;
075    import org.apache.directory.shared.ldap.constants.JndiPropertyConstants;
076    import org.apache.directory.shared.ldap.constants.SchemaConstants;
077    import org.apache.directory.shared.ldap.cursor.EmptyCursor;
078    import org.apache.directory.shared.ldap.cursor.SingletonCursor;
079    import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
080    import org.apache.directory.shared.ldap.entry.EntryAttribute;
081    import org.apache.directory.shared.ldap.entry.Modification;
082    import org.apache.directory.shared.ldap.entry.ServerEntry;
083    import org.apache.directory.shared.ldap.exception.LdapException;
084    import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException;
085    import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
086    import org.apache.directory.shared.ldap.filter.EqualityNode;
087    import org.apache.directory.shared.ldap.filter.ExprNode;
088    import org.apache.directory.shared.ldap.filter.PresenceNode;
089    import org.apache.directory.shared.ldap.filter.SearchScope;
090    import org.apache.directory.shared.ldap.jndi.JndiUtils;
091    import org.apache.directory.shared.ldap.message.AliasDerefMode;
092    import org.apache.directory.shared.ldap.name.AVA;
093    import org.apache.directory.shared.ldap.name.DN;
094    import org.apache.directory.shared.ldap.name.RDN;
095    import org.apache.directory.shared.ldap.util.AttributeUtils;
096    import org.apache.directory.shared.ldap.util.StringTools;
097    
098    
099    /**
100     * A non-federated abstract Context implementation.
101     *
102     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
103     * @version $Rev: 927404 $
104     */
105    public abstract class ServerContext implements EventContext
106    {
107        /** property key used for deleting the old RDN on a rename */
108        public static final String DELETE_OLD_RDN_PROP = JndiPropertyConstants.JNDI_LDAP_DELETE_RDN;
109    
110        /** Empty array of controls for use in dealing with them */
111        protected static final Control[] EMPTY_CONTROLS = new Control[0];
112    
113        /** The directory service which owns this context **/
114        private final DirectoryService service;
115    
116        /** The cloned environment used by this Context */
117        private final Hashtable<String, Object> env;
118    
119        /** The distinguished name of this Context */
120        private final DN dn;
121    
122        /** The set of registered NamingListeners */
123        private final Map<NamingListener,DirectoryListener> listeners = 
124            new HashMap<NamingListener,DirectoryListener>();
125    
126        /** The request controls to set on operations before performing them */
127        protected Control[] requestControls = EMPTY_CONTROLS;
128    
129        /** The response controls to set after performing operations */
130        protected Control[] responseControls = EMPTY_CONTROLS;
131    
132        /** Connection level controls associated with the session */
133        protected Control[] connectControls = EMPTY_CONTROLS;
134        
135        private final CoreSession session;
136    
137    
138        // ------------------------------------------------------------------------
139        // Constructors
140        // ------------------------------------------------------------------------
141    
142        
143        /**
144         * Must be called by all subclasses to initialize the nexus proxy and the
145         * environment settings to be used by this Context implementation.  This
146         * specific contstructor relies on the presence of the {@link
147         * Context#PROVIDER_URL} key and value to determine the distinguished name
148         * of the newly created context.  It also checks to make sure the
149         * referenced name actually exists within the system.  This constructor
150         * is used for all InitialContext requests.
151         * 
152         * @param service the parent service that manages this context
153         * @param env the environment properties used by this context.
154         * @throws NamingException if the environment parameters are not set 
155         * correctly.
156         */
157        protected ServerContext( DirectoryService service, Hashtable<String, Object> env ) throws Exception
158        {
159            this.service = service;
160    
161            this.env = env;
162            
163            LdapJndiProperties props = LdapJndiProperties.getLdapJndiProperties( this.env );
164            dn = props.getProviderDn();
165    
166            /*
167             * Need do bind operation here, and opContext returned contains the 
168             * newly created session.
169             */
170            BindOperationContext opContext = doBindOperation( props.getBindDn(), props.getCredentials(), 
171                props.getSaslMechanism(), props.getSaslAuthId() );
172    
173            session = opContext.getSession();
174            OperationManager operationManager = service.getOperationManager();
175            
176            if ( ! operationManager.hasEntry( new EntryOperationContext( session, dn ) ) )
177            {
178                throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
179            }
180        }
181        
182        
183        /**
184         * Must be called by all subclasses to initialize the nexus proxy and the
185         * environment settings to be used by this Context implementation.  This
186         * constructor is used to propagate new contexts from existing contexts.
187         *
188         * @param principal the directory user principal that is propagated
189         * @param dn the distinguished name of this context
190         * @param service the directory service core
191         * @throws NamingException if there is a problem creating the new context
192         */
193        public ServerContext( DirectoryService service, LdapPrincipal principal, Name name ) throws Exception
194        {
195            this.service = service;
196            this.dn = (DN)(DN.fromName( name ).clone());
197    
198            this.env = new Hashtable<String, Object>();
199            this.env.put( PROVIDER_URL, dn.toString() );
200            this.env.put( DirectoryService.JNDI_KEY, service );
201            session = new DefaultCoreSession( principal, service );
202            OperationManager operationManager = service.getOperationManager();
203            
204            if ( ! operationManager.hasEntry( new EntryOperationContext( session, dn ) ) )
205            {
206                throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
207            }
208        }
209    
210    
211        public ServerContext( DirectoryService service, CoreSession session, Name name ) throws Exception
212        {
213            this.service = service;
214            this.dn = (DN)(DN.fromName( name ).clone());
215            this.env = new Hashtable<String, Object>();
216            this.env.put( PROVIDER_URL, dn.toString() );
217            this.env.put( DirectoryService.JNDI_KEY, service );
218            this.session = session;
219            OperationManager operationManager = service.getOperationManager();
220            
221            if ( ! operationManager.hasEntry( new EntryOperationContext( session, ( DN ) dn ) ) )
222            {
223                throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
224            }
225        }
226    
227    
228        /**
229         * Set the referral handling flag into the operation context using
230         * the JNDI value stored into the environment.
231         */
232        protected void injectReferralControl( OperationContext opCtx )
233        {
234            if ( "ignore".equalsIgnoreCase( (String)env.get( Context.REFERRAL ) ) )
235            {
236                opCtx.ignoreReferral();
237            }
238            else if ( "throw".equalsIgnoreCase( (String)env.get( Context.REFERRAL ) ) )
239            {
240                opCtx.throwReferral();
241            }
242            else
243            {
244                // TODO : handle the 'follow' referral option 
245                opCtx.throwReferral();
246            }
247        }
248        // ------------------------------------------------------------------------
249        // Protected Methods for Operations
250        // ------------------------------------------------------------------------
251        // Use these methods instead of manually calling the nexusProxy so we can
252        // add request controls to operation contexts before the call and extract 
253        // response controls from the contexts after the call.  NOTE that the 
254        // JndiUtils.fromJndiControls( requestControls ) must be cleared after each operation.  This makes a 
255        // context not thread safe.
256        // ------------------------------------------------------------------------
257    
258        /**
259         * Used to encapsulate [de]marshalling of controls before and after add operations.
260         * @param entry
261         * @param target
262         */
263        protected void doAddOperation( DN target, ServerEntry entry ) throws Exception
264        {
265            // setup the op context and populate with request controls
266            AddOperationContext opCtx = new AddOperationContext( session, entry );
267    
268            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
269            
270            // Inject the referral handling into the operation context
271            injectReferralControl( opCtx );
272            
273            // execute add operation
274            OperationManager operationManager = service.getOperationManager();
275            operationManager.add( opCtx );
276        
277            // clear the request controls and set the response controls 
278            requestControls = EMPTY_CONTROLS;
279            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
280        }
281    
282    
283        /**
284         * Used to encapsulate [de]marshalling of controls before and after delete operations.
285         * @param target
286         */
287        protected void doDeleteOperation( DN target ) throws Exception
288        {
289            // setup the op context and populate with request controls
290            DeleteOperationContext opCtx = new DeleteOperationContext( session, target );
291    
292            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
293    
294            // Inject the referral handling into the operation context
295            injectReferralControl( opCtx );
296    
297            // execute delete operation
298            OperationManager operationManager = service.getOperationManager();
299            operationManager.delete( opCtx );
300    
301            // clear the request controls and set the response controls 
302            requestControls = EMPTY_CONTROLS;
303            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
304        }
305    
306    
307        /**
308         * Used to encapsulate [de]marshalling of controls before and after list operations.
309         * @param dn
310         * @param aliasDerefMode
311         * @param filter
312         * @param searchControls
313         * @return NamingEnumeration
314         */
315        protected EntryFilteringCursor doSearchOperation( DN dn, AliasDerefMode aliasDerefMode,
316            ExprNode filter, SearchControls searchControls ) throws Exception
317        {
318            OperationManager operationManager = service.getOperationManager();
319            EntryFilteringCursor results = null;
320            OperationContext opContext;
321            
322            Object typesOnlyObj = getEnvironment().get( "java.naming.ldap.typesOnly" );
323            boolean typesOnly = false;
324            if( typesOnlyObj != null )
325            {
326                typesOnly = Boolean.parseBoolean( typesOnlyObj.toString() );
327            }
328    
329            // We have to check if it's a compare operation or a search. 
330            // A compare operation has a OBJECT scope search, the filter must
331            // be of the form (object=value) (no wildcards), and no attributes
332            // should be asked to be returned.
333            if ( ( searchControls.getSearchScope() == SearchControls.OBJECT_SCOPE )
334                && ( ( searchControls.getReturningAttributes() != null )
335                    && ( searchControls.getReturningAttributes().length == 0 ) )
336                && ( filter instanceof EqualityNode ) )
337            {
338                opContext = new CompareOperationContext( session, dn, ((EqualityNode)filter).getAttribute(), ((EqualityNode)filter).getValue() );
339                
340                // Inject the referral handling into the operation context
341                injectReferralControl( opContext );
342    
343                // Call the operation
344                boolean result = operationManager.compare( (CompareOperationContext)opContext );
345    
346                // setup the op context and populate with request controls
347                opContext = new SearchOperationContext( session, dn, filter,
348                    searchControls );
349                ((SearchOperationContext)opContext).setAliasDerefMode( aliasDerefMode );
350                opContext.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
351                
352                ( ( SearchOperationContext ) opContext ).setTypesOnly(  typesOnly );
353                
354                if ( result )
355                {
356                    ServerEntry emptyEntry = new DefaultServerEntry( service.getSchemaManager(), DN.EMPTY_DN ); 
357                    return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( emptyEntry ), (SearchOperationContext)opContext );
358                }
359                else
360                {
361                    return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), (SearchOperationContext)opContext );
362                }
363            }
364            else
365            {
366                // It's a Search
367                
368                // setup the op context and populate with request controls
369                opContext = new SearchOperationContext( session, dn, filter, searchControls );
370                ( ( SearchOperationContext ) opContext ).setAliasDerefMode( aliasDerefMode );
371                opContext.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
372                ( ( SearchOperationContext ) opContext ).setTypesOnly(  typesOnly );
373                
374                // Inject the referral handling into the operation context
375                injectReferralControl( opContext );
376    
377                // execute search operation
378                results = operationManager.search( (SearchOperationContext)opContext );
379            }
380    
381            // clear the request controls and set the response controls 
382            requestControls = EMPTY_CONTROLS;
383            responseControls = JndiUtils.toJndiControls( opContext.getResponseControls() );
384    
385            return results;
386        }
387    
388    
389        /**
390         * Used to encapsulate [de]marshalling of controls before and after list operations.
391         */
392        protected EntryFilteringCursor doListOperation( DN target ) throws Exception
393        {
394            // setup the op context and populate with request controls
395            ListOperationContext opCtx = new ListOperationContext( session, target );
396            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
397    
398            // execute list operation
399            OperationManager operationManager = service.getOperationManager();
400            EntryFilteringCursor results = operationManager.list( opCtx );
401    
402            // clear the request controls and set the response controls 
403            requestControls = EMPTY_CONTROLS;
404            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
405    
406            return results;
407        }
408    
409    
410        protected ServerEntry doGetRootDSEOperation( DN target ) throws Exception
411        {
412            GetRootDSEOperationContext opCtx = new GetRootDSEOperationContext( session, target );
413            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
414    
415            // do not reset request controls since this is not an external 
416            // operation and not do bother setting the response controls either
417            OperationManager operationManager = service.getOperationManager();
418            return operationManager.getRootDSE( opCtx );
419        }
420    
421    
422        /**
423         * Used to encapsulate [de]marshalling of controls before and after lookup operations.
424         */
425        protected ServerEntry doLookupOperation( DN target ) throws Exception
426        {
427            // setup the op context and populate with request controls
428            LookupOperationContext opCtx;
429    
430            // execute lookup/getRootDSE operation
431            opCtx = new LookupOperationContext( session, target );
432            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
433            OperationManager operationManager = service.getOperationManager();
434            ServerEntry serverEntry = operationManager.lookup( opCtx );
435    
436            // clear the request controls and set the response controls 
437            requestControls = EMPTY_CONTROLS;
438            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
439            return serverEntry;
440        }
441    
442    
443        /**
444         * Used to encapsulate [de]marshalling of controls before and after lookup operations.
445         */
446        protected ServerEntry doLookupOperation( DN target, String[] attrIds ) throws Exception
447        {
448            // setup the op context and populate with request controls
449            LookupOperationContext opCtx;
450    
451            // execute lookup/getRootDSE operation
452            opCtx = new LookupOperationContext( session, target, attrIds );
453            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
454            OperationManager operationManager = service.getOperationManager();
455            ClonedServerEntry serverEntry = operationManager.lookup( opCtx );
456    
457            // clear the request controls and set the response controls 
458            requestControls = EMPTY_CONTROLS;
459            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
460    
461            // Now remove the ObjectClass attribute if it has not been requested
462            if ( ( opCtx.getAttrsId() != null ) && ( opCtx.getAttrsId().size() != 0 ) )
463            {
464                if ( ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ) != null )
465                    && ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ).size() == 0 ) )
466                {
467                    serverEntry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
468                }
469            }
470    
471            return serverEntry;
472        }
473    
474    
475        /**
476         * Used to encapsulate [de]marshalling of controls before and after bind operations.
477         */
478        protected BindOperationContext doBindOperation( DN bindDn, byte[] credentials, String saslMechanism, 
479            String saslAuthId ) throws Exception
480        {
481            // setup the op context and populate with request controls
482            BindOperationContext opCtx = new BindOperationContext( null );
483            opCtx.setDn( bindDn );
484            opCtx.setCredentials( credentials );
485            opCtx.setSaslMechanism( saslMechanism );
486            opCtx.setSaslAuthId( saslAuthId );
487            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
488    
489            // execute bind operation
490            OperationManager operationManager = service.getOperationManager();
491            operationManager.bind( opCtx );
492    
493            // clear the request controls and set the response controls 
494            requestControls = EMPTY_CONTROLS;
495            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
496            return opCtx;
497        }
498    
499    
500        /**
501         * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
502         */
503        protected void doMoveAndRenameOperation( DN oldDn, DN parent, RDN newRdn, boolean delOldDn )
504            throws Exception
505        {
506            // setup the op context and populate with request controls
507            MoveAndRenameOperationContext opCtx = new MoveAndRenameOperationContext( session, oldDn, parent, new RDN(
508                newRdn ), delOldDn );
509            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
510    
511            // Inject the referral handling into the operation context
512            injectReferralControl( opCtx );
513            
514            // execute moveAndRename operation
515            OperationManager operationManager = service.getOperationManager();
516            operationManager.moveAndRename( opCtx );
517    
518            // clear the request controls and set the response controls 
519            requestControls = EMPTY_CONTROLS;
520            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
521        }
522    
523    
524        /**
525         * Used to encapsulate [de]marshalling of controls before and after modify operations.
526         */
527        protected void doModifyOperation( DN dn, List<Modification> modifications ) throws Exception
528        {
529            // setup the op context and populate with request controls
530            ModifyOperationContext opCtx = new ModifyOperationContext( session, dn, modifications );
531            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
532    
533            // Inject the referral handling into the operation context
534            injectReferralControl( opCtx );
535            
536            // execute modify operation
537            OperationManager operationManager = service.getOperationManager();
538            operationManager.modify( opCtx );
539    
540            // clear the request controls and set the response controls 
541            requestControls = EMPTY_CONTROLS;
542            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
543        }
544    
545    
546        /**
547         * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
548         */
549        protected void doMove( DN oldDn, DN target ) throws Exception
550        {
551            // setup the op context and populate with request controls
552            MoveOperationContext opCtx = new MoveOperationContext( session, oldDn, target );
553            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
554    
555            // Inject the referral handling into the operation context
556            injectReferralControl( opCtx );
557            
558            // execute move operation
559            OperationManager operationManager = service.getOperationManager();
560            operationManager.move( opCtx );
561    
562            // clear the request controls and set the response controls 
563            requestControls = EMPTY_CONTROLS;
564            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
565        }
566    
567    
568        /**
569         * Used to encapsulate [de]marshalling of controls before and after rename operations.
570         */
571        protected void doRename( DN oldDn, RDN newRdn, boolean delOldRdn ) throws Exception
572        {
573            // setup the op context and populate with request controls
574            RenameOperationContext opCtx = new RenameOperationContext( session, oldDn, newRdn, delOldRdn );
575            opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
576    
577            // Inject the referral handling into the operation context
578            injectReferralControl( opCtx );
579            
580            // execute rename operation
581            OperationManager operationManager = service.getOperationManager();
582            operationManager.rename( opCtx );
583    
584            // clear the request controls and set the response controls 
585            requestControls = EMPTY_CONTROLS;
586            responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
587        }
588    
589        
590        public CoreSession getSession()
591        {
592            return session;
593        }
594        
595        
596        public DirectoryService getDirectoryService()
597        {
598            return service;
599        }
600        
601        
602        // ------------------------------------------------------------------------
603        // New Impl Specific Public Methods
604        // ------------------------------------------------------------------------
605    
606        /**
607         * Gets a handle on the root context of the DIT.  The RootDSE as the present user.
608         *
609         * @return the rootDSE context
610         * @throws NamingException if this fails
611         */
612        public abstract ServerContext getRootContext() throws NamingException;
613    
614    
615        /**
616         * Gets the {@link DirectoryService} associated with this context.
617         *
618         * @return the directory service associated with this context
619         */
620        public DirectoryService getService()
621        {
622            return service;
623        }
624    
625    
626        // ------------------------------------------------------------------------
627        // Protected Accessor Methods
628        // ------------------------------------------------------------------------
629    
630        
631        /**
632         * Gets the distinguished name of the entry associated with this Context.
633         * 
634         * @return the distinguished name of this Context's entry.
635         */
636        protected DN getDn()
637        {
638            return dn;
639        }
640    
641    
642        // ------------------------------------------------------------------------
643        // JNDI Context Interface Methods
644        // ------------------------------------------------------------------------
645    
646        /**
647         * @see javax.naming.Context#close()
648         */
649        public void close() throws NamingException
650        {
651            for ( DirectoryListener listener : listeners.values() )
652            {
653                try
654                {
655                    service.getEventService().removeListener( listener );
656                }
657                catch ( Exception e )
658                {
659                    JndiUtils.wrap( e );
660                }
661            }
662            
663            listeners.clear();
664        }
665    
666    
667        /**
668         * @see javax.naming.Context#getNameInNamespace()
669         */
670        public String getNameInNamespace() throws NamingException
671        {
672            return dn.getName();
673        }
674    
675    
676        /**
677         * @see javax.naming.Context#getEnvironment()
678         */
679        public Hashtable<String, Object> getEnvironment()
680        {
681            return env;
682        }
683    
684    
685        /**
686         * @see javax.naming.Context#addToEnvironment(java.lang.String, 
687         * java.lang.Object)
688         */
689        public Object addToEnvironment( String propName, Object propVal ) throws NamingException
690        {
691            return env.put( propName, propVal );
692        }
693    
694    
695        /**
696         * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
697         */
698        public Object removeFromEnvironment( String propName ) throws NamingException
699        {
700            return env.remove( propName );
701        }
702    
703    
704        /**
705         * @see javax.naming.Context#createSubcontext(java.lang.String)
706         */
707        public Context createSubcontext( String name ) throws NamingException
708        {
709            return createSubcontext( new LdapName( name ) );
710        }
711    
712    
713        /**
714         * @see javax.naming.Context#createSubcontext(javax.naming.Name)
715         */
716        public Context createSubcontext( Name name ) throws NamingException
717        {
718            DN target = buildTarget( DN.fromName( name ) );
719            ServerEntry serverEntry = service.newEntry( target );
720            
721            try
722            {
723                serverEntry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, JavaLdapSupport.JCONTAINER_ATTR );
724            }
725            catch ( LdapException le )
726            {
727                throw new SchemaViolationException( I18n.err( I18n.ERR_491, name) );
728            }
729    
730            // Now add the CN attribute, which is mandatory
731            RDN rdn = target.getRdn();
732    
733            if ( rdn != null )
734            {
735                if ( SchemaConstants.CN_AT.equals( rdn.getNormType() ) )
736                {
737                    serverEntry.put( rdn.getUpType(), ( String ) rdn.getUpValue() );
738                }
739                else
740                {
741                    // No CN in the rdn, this is an error
742                    throw new SchemaViolationException( I18n.err( I18n.ERR_491, name) );
743                }
744            }
745            else
746            {
747                // No CN in the rdn, this is an error
748                throw new SchemaViolationException( I18n.err( I18n.ERR_491, name) );
749            }
750    
751            /*
752             * Add the new context to the server which as a side effect adds 
753             * operational attributes to the serverEntry refering instance which
754             * can them be used to initialize a new ServerLdapContext.  Remember
755             * we need to copy over the controls as well to propagate the complete 
756             * environment besides what's in the hashtable for env.
757             */
758            try
759            {
760                doAddOperation( target, serverEntry );
761            }
762            catch ( Exception e )
763            {
764                JndiUtils.wrap( e );
765            }
766            
767            ServerLdapContext ctx = null;
768            
769            try
770            {
771                ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), DN.toName( target ) );
772            }
773            catch ( Exception e )
774            {
775                JndiUtils.wrap( e );
776            }
777            
778            return ctx;
779        }
780    
781    
782        /**
783         * @see javax.naming.Context#destroySubcontext(java.lang.String)
784         */
785        public void destroySubcontext( String name ) throws NamingException
786        {
787            destroySubcontext( new LdapName( name ) );
788        }
789    
790    
791        /**
792         * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
793         */
794        public void destroySubcontext( Name name ) throws NamingException
795        {
796            DN target = buildTarget( DN.fromName( name ) );
797    
798            if ( target.size() == 0 )
799            {
800                throw new NoPermissionException( I18n.err( I18n.ERR_492 ) );
801            }
802    
803            try
804            {
805                doDeleteOperation( target );
806            }
807            catch ( Exception e )
808            {
809                JndiUtils.wrap( e );
810            }
811        }
812    
813    
814        /**
815         * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
816         */
817        public void bind( String name, Object obj ) throws NamingException
818        {
819            bind( new LdapName( name ), obj );
820        }
821    
822    
823        private void injectRdnAttributeValues( DN target, ServerEntry serverEntry ) throws NamingException
824        {
825            // Add all the RDN attributes and their values to this entry
826            RDN rdn = target.getRdn( target.size() - 1 );
827    
828            if ( rdn.size() == 1 )
829            {
830                serverEntry.put( rdn.getUpType(), ( String ) rdn.getNormValue() );
831            }
832            else
833            {
834                for ( AVA atav : rdn )
835                {
836                    serverEntry.put( atav.getUpType(), atav.getNormValue().getString() );
837                }
838            }
839        }
840    
841    
842        /**
843         * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
844         */
845        public void bind( Name name, Object obj ) throws NamingException
846        {
847            // First, use state factories to do a transformation
848            DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );
849    
850            DN target = buildTarget( DN.fromName( name ) );
851    
852            // let's be sure that the Attributes is case insensitive
853            ServerEntry outServerEntry = null;
854            
855            try
856            {
857                outServerEntry = ServerEntryUtils.toServerEntry( AttributeUtils.toCaseInsensitive( res
858                    .getAttributes() ), target, service.getSchemaManager() );
859            }
860            catch ( LdapInvalidAttributeTypeException liate )
861            {
862                throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
863            }
864    
865            if ( outServerEntry != null )
866            {
867                try
868                {
869                    doAddOperation( target, outServerEntry );
870                }
871                catch ( Exception e )
872                {
873                    JndiUtils.wrap( e );
874                }
875                return;
876            }
877    
878            if ( obj instanceof ServerEntry )
879            {
880                try
881                {
882                    doAddOperation( target, ( ServerEntry ) obj );
883                }
884                catch ( Exception e )
885                {
886                    JndiUtils.wrap( e );
887                }
888            }
889            // Check for Referenceable
890            else if ( obj instanceof Referenceable )
891            {
892                throw new NamingException( I18n.err( I18n.ERR_493 ) );
893            }
894            // Store different formats
895            else if ( obj instanceof Reference )
896            {
897                // Store as ref and add outAttrs
898                throw new NamingException( I18n.err( I18n.ERR_494 ) );
899            }
900            else if ( obj instanceof Serializable )
901            {
902                // Serialize and add outAttrs
903                ServerEntry serverEntry = service.newEntry( target );
904    
905                if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
906                {
907                    for ( EntryAttribute serverAttribute : outServerEntry )
908                    {
909                        try
910                        {
911                            serverEntry.put( serverAttribute );
912                        }
913                        catch ( LdapException le )
914                        {
915                            throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
916                        }
917                    }
918                }
919    
920                // Get target and inject all rdn attributes into entry
921                injectRdnAttributeValues( target, serverEntry );
922    
923                // Serialize object into entry attributes and add it.
924                try
925                { 
926                    JavaLdapSupport.serialize( serverEntry, obj, service.getSchemaManager() );
927                }
928                catch ( LdapException le )
929                {
930                    throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
931                }
932                
933                try
934                {
935                    doAddOperation( target, serverEntry );
936                }
937                catch ( Exception e )
938                {
939                    JndiUtils.wrap( e );
940                }
941            }
942            else if ( obj instanceof DirContext )
943            {
944                // Grab attributes and merge with outAttrs
945                ServerEntry serverEntry = null;
946                
947                try
948                { 
949                    serverEntry = ServerEntryUtils.toServerEntry( ( ( DirContext ) obj ).getAttributes( "" ),
950                        target, service.getSchemaManager() );
951                }
952                catch ( LdapInvalidAttributeTypeException liate )
953                {
954                    throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
955                }
956    
957                if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
958                {
959                    for ( EntryAttribute serverAttribute : outServerEntry )
960                    {
961                        try
962                        {                 
963                            serverEntry.put( serverAttribute );
964                        }
965                        catch ( LdapException le )
966                        {
967                            throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
968                        }
969                    }
970                }
971    
972                injectRdnAttributeValues( target, serverEntry );
973                try
974                {
975                    doAddOperation( target, serverEntry );
976                }
977                catch ( Exception e )
978                {
979                    JndiUtils.wrap( e );
980                }
981            }
982            else
983            {
984                throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
985            }
986        }
987    
988    
989        /**
990         * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
991         */
992        public void rename( String oldName, String newName ) throws NamingException
993        {
994            rename( new LdapName( oldName ), new LdapName( newName ) );
995        }
996    
997    
998        /**
999         * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
1000         */
1001        public void rename( Name oldName, Name newName ) throws NamingException
1002        {
1003            DN oldDn = buildTarget( DN.fromName( oldName ) );
1004            DN newDn = buildTarget( DN.fromName( newName ) );
1005    
1006            if ( oldDn.size() == 0 )
1007            {
1008                throw new NoPermissionException( I18n.err( I18n.ERR_312 ) );
1009            }
1010    
1011            // calculate parents
1012            DN oldParent = (DN)oldDn.clone();
1013            
1014            try
1015            {
1016                oldParent.remove( oldDn.size() - 1 );
1017            }
1018            catch ( LdapInvalidDnException lide )
1019            {
1020                throw new NamingException( I18n.err( I18n.ERR_313, lide.getMessage() ) );
1021            }
1022            
1023            DN newParent = ( DN ) newDn.clone();
1024            
1025            try
1026            {
1027                newParent.remove( newDn.size() - 1 );
1028            }
1029            catch ( LdapInvalidDnException lide )
1030            {
1031                throw new NamingException( I18n.err( I18n.ERR_313, lide.getMessage() ) );
1032            }
1033    
1034    
1035            RDN oldRdn = oldDn.getRdn();
1036            RDN newRdn = newDn.getRdn();
1037            boolean delOldRdn = true;
1038    
1039            /*
1040             * Attempt to use the java.naming.ldap.deleteRDN environment property
1041             * to get an override for the deleteOldRdn option to modifyRdn.  
1042             */
1043            if ( null != env.get( DELETE_OLD_RDN_PROP ) )
1044            {
1045                String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
1046                delOldRdn = !delOldRdnStr.equalsIgnoreCase( "false" ) && !delOldRdnStr.equalsIgnoreCase( "no" )
1047                    && !delOldRdnStr.equals( "0" );
1048            }
1049    
1050            /*
1051             * We need to determine if this rename operation corresponds to a simple
1052             * RDN name change or a move operation.  If the two names are the same
1053             * except for the RDN then it is a simple modifyRdn operation.  If the
1054             * names differ in size or have a different baseDN then the operation is
1055             * a move operation.  Furthermore if the RDN in the move operation 
1056             * changes it is both an RDN change and a move operation.
1057             */
1058            if ( oldParent.equals( newParent ) )
1059            {
1060                try
1061                {
1062                    doRename( oldDn, newRdn, delOldRdn );
1063                }
1064                catch ( Exception e )
1065                {
1066                    JndiUtils.wrap( e );
1067                }
1068            }
1069            else
1070            {
1071                if ( newRdn.equals( oldRdn ) )
1072                {
1073                    try
1074                    {
1075                        doMove( oldDn, newParent );
1076                    }
1077                    catch ( Exception e )
1078                    {
1079                        JndiUtils.wrap( e );
1080                    }
1081                }
1082                else
1083                {
1084                    try
1085                    {
1086                        doMoveAndRenameOperation( oldDn, newParent, newRdn, delOldRdn );
1087                    }
1088                    catch ( Exception e )
1089                    {
1090                        JndiUtils.wrap( e );
1091                    }
1092                }
1093            }
1094        }
1095    
1096    
1097        /**
1098         * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
1099         */
1100        public void rebind( String name, Object obj ) throws NamingException
1101        {
1102            rebind( new LdapName( name ), obj );
1103        }
1104    
1105    
1106        /**
1107         * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
1108         */
1109        public void rebind( Name name, Object obj ) throws NamingException
1110        {
1111            DN target = buildTarget( DN.fromName( name ) );
1112            OperationManager operationManager = service.getOperationManager();
1113            
1114            try
1115            {
1116                if ( operationManager.hasEntry( new EntryOperationContext( session, target ) ) )
1117                {
1118                    doDeleteOperation( target );
1119                }
1120            }
1121            catch ( Exception e )
1122            {
1123                JndiUtils.wrap( e );
1124            }
1125    
1126            bind( name, obj );
1127        }
1128    
1129    
1130        /**
1131         * @see javax.naming.Context#unbind(java.lang.String)
1132         */
1133        public void unbind( String name ) throws NamingException
1134        {
1135            unbind( new LdapName( name ) );
1136        }
1137    
1138    
1139        /**
1140         * @see javax.naming.Context#unbind(javax.naming.Name)
1141         */
1142        public void unbind( Name name ) throws NamingException
1143        {
1144            try
1145            {
1146                doDeleteOperation( buildTarget( DN.fromName( name ) ) );
1147            }
1148            catch ( Exception e )
1149            {
1150                JndiUtils.wrap( e );
1151            }
1152        }
1153    
1154    
1155        /**
1156         * @see javax.naming.Context#lookup(java.lang.String)
1157         */
1158        public Object lookup( String name ) throws NamingException
1159        {
1160            if ( StringTools.isEmpty( name ) )
1161            {
1162                return lookup( new LdapName( "" ) );
1163            }
1164            else
1165            {
1166                return lookup( new LdapName( name ) );
1167            }
1168        }
1169    
1170    
1171        /**
1172         * @see javax.naming.Context#lookup(javax.naming.Name)
1173         */
1174        public Object lookup( Name name ) throws NamingException
1175        {
1176            Object obj;
1177            DN target = buildTarget( DN.fromName( name ) );
1178    
1179            ServerEntry serverEntry = null;
1180    
1181            try
1182            {
1183                if ( name.size() == 0 )
1184                {
1185                    serverEntry = doGetRootDSEOperation( target );
1186                }
1187                else
1188                {
1189                    serverEntry = doLookupOperation( target );
1190                }
1191            }
1192            catch ( Exception e )
1193            {
1194                JndiUtils.wrap( e );
1195            }
1196    
1197            try
1198            {
1199                obj = DirectoryManager.getObjectInstance( null, name, this, env, 
1200                    ServerEntryUtils.toBasicAttributes( serverEntry ) );
1201            }
1202            catch ( Exception e )
1203            {
1204                String msg = I18n.err( I18n.ERR_497, target );
1205                NamingException ne = new NamingException( msg );
1206                ne.setRootCause( e );
1207                throw ne;
1208            }
1209    
1210            if ( obj != null )
1211            {
1212                return obj;
1213            }
1214    
1215            // First lets test and see if the entry is a serialized java object
1216            if ( serverEntry.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
1217            {
1218                // Give back serialized object and not a context
1219                return JavaLdapSupport.deserialize( serverEntry );
1220            }
1221    
1222            // Initialize and return a context since the entry is not a java object
1223            ServerLdapContext ctx = null;
1224            
1225            try
1226            {
1227                ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), DN.toName( target ) );
1228            }
1229            catch ( Exception e )
1230            {
1231                JndiUtils.wrap( e );
1232            }
1233            
1234            return ctx;
1235        }
1236    
1237    
1238        /**
1239         * @see javax.naming.Context#lookupLink(java.lang.String)
1240         */
1241        public Object lookupLink( String name ) throws NamingException
1242        {
1243            throw new UnsupportedOperationException();
1244        }
1245    
1246    
1247        /**
1248         * @see javax.naming.Context#lookupLink(javax.naming.Name)
1249         */
1250        public Object lookupLink( Name name ) throws NamingException
1251        {
1252            throw new UnsupportedOperationException();
1253        }
1254    
1255    
1256        /**
1257         * Non-federated implementation presuming the name argument is not a 
1258         * composite name spanning multiple namespaces but a compound name in 
1259         * the same LDAP namespace.  Hence the parser returned is always the
1260         * same as calling this method with the empty String. 
1261         * 
1262         * @see javax.naming.Context#getNameParser(java.lang.String)
1263         */
1264        public NameParser getNameParser( String name ) throws NamingException
1265        {
1266            return new NameParser()
1267            {
1268                public Name parse( String name ) throws NamingException
1269                {
1270                    try
1271                    {
1272                        return DN.toName( new DN( name ) );
1273                    }
1274                    catch ( LdapInvalidDnException lide )
1275                    {
1276                        throw new InvalidNameException( lide.getMessage() );
1277                    }
1278                }
1279            };
1280        }
1281    
1282    
1283        /**
1284         * Non-federated implementation presuming the name argument is not a 
1285         * composite name spanning multiple namespaces but a compound name in 
1286         * the same LDAP namespace.  Hence the parser returned is always the
1287         * same as calling this method with the empty String Name.
1288         * 
1289         * @see javax.naming.Context#getNameParser(javax.naming.Name)
1290         */
1291        public NameParser getNameParser( final Name name ) throws NamingException
1292        {
1293            return new NameParser()
1294            {
1295                public Name parse( String n ) throws NamingException
1296                {
1297                    try
1298                    {
1299                        return DN.toName( new DN( name.toString() ) );
1300                    }
1301                    catch ( LdapInvalidDnException lide )
1302                    {
1303                        throw new InvalidNameException( lide.getMessage() );
1304                    }
1305                }
1306            };
1307        }
1308    
1309    
1310        /**
1311         * @see javax.naming.Context#list(java.lang.String)
1312         */
1313        @SuppressWarnings(value =
1314            { "unchecked" })
1315        public NamingEnumeration list( String name ) throws NamingException
1316        {
1317            return list( new LdapName( name ) );
1318        }
1319    
1320    
1321        /**
1322         * @see javax.naming.Context#list(javax.naming.Name)
1323         */
1324        @SuppressWarnings(value =
1325            { "unchecked" })
1326        public NamingEnumeration list( Name name ) throws NamingException
1327        {
1328            try
1329            {
1330                return new NamingEnumerationAdapter( doListOperation( buildTarget( DN.fromName( name ) ) ) );
1331            }
1332            catch ( Exception e )
1333            {
1334                JndiUtils.wrap( e );
1335                return null; // shut up compiler
1336            }
1337        }
1338    
1339    
1340        /**
1341         * @see javax.naming.Context#listBindings(java.lang.String)
1342         */
1343        @SuppressWarnings(value =
1344            { "unchecked" })
1345        public NamingEnumeration listBindings( String name ) throws NamingException
1346        {
1347            return listBindings( new LdapName( name ) );
1348        }
1349    
1350    
1351        /**
1352         * @see javax.naming.Context#listBindings(javax.naming.Name)
1353         */
1354        @SuppressWarnings(value =
1355            { "unchecked" })
1356        public NamingEnumeration listBindings( Name name ) throws NamingException
1357        {
1358            // Conduct a special one level search at base for all objects
1359            DN base = buildTarget( DN.fromName( name ) );
1360            PresenceNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
1361            SearchControls ctls = new SearchControls();
1362            ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1363            AliasDerefMode aliasDerefMode = AliasDerefMode.getEnum( getEnvironment() );
1364            try
1365            {
1366                return new NamingEnumerationAdapter( doSearchOperation( base, aliasDerefMode, filter, ctls ) );
1367            }
1368            catch ( Exception e )
1369            {
1370                JndiUtils.wrap( e );
1371                return null; // shutup compiler
1372            }
1373        }
1374    
1375    
1376        /**
1377         * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
1378         */
1379        public String composeName( String name, String prefix ) throws NamingException
1380        {
1381            return composeName( new LdapName( name ), new LdapName( prefix ) ).toString();
1382        }
1383    
1384    
1385        /**
1386         * @see javax.naming.Context#composeName(javax.naming.Name,
1387         * javax.naming.Name)
1388         */
1389        public Name composeName( Name name, Name prefix ) throws NamingException
1390        {
1391            // No prefix reduces to name, or the name relative to this context
1392            if ( prefix == null || prefix.size() == 0 )
1393            {
1394                return name;
1395            }
1396    
1397            /*
1398             * Example: This context is ou=people and say name is the relative
1399             * name of uid=jwalker and the prefix is dc=domain.  Then we must
1400             * compose the name relative to prefix which would be:
1401             * 
1402             * uid=jwalker,ou=people,dc=domain.
1403             * 
1404             * The following general algorithm generates the right name:
1405             *      1). Find the Dn for name and walk it from the head to tail
1406             *          trying to match for the head of prefix.
1407             *      2). Remove name components from the Dn until a match for the 
1408             *          head of the prefix is found.
1409             *      3). Return the remainder of the fqn or Dn after chewing off some
1410             */
1411    
1412            // 1). Find the Dn for name and walk it from the head to tail
1413            DN fqn = buildTarget( DN.fromName( name ) );
1414            String head = prefix.get( 0 );
1415    
1416            // 2). Walk the fqn trying to match for the head of the prefix
1417            while ( fqn.size() > 0 )
1418            {
1419                // match found end loop
1420                if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
1421                {
1422                    return DN.toName( fqn );
1423                }
1424                else
1425                // 2). Remove name components from the Dn until a match 
1426                {
1427                    try
1428                    {
1429                        fqn.remove( 0 );
1430                    }
1431                    catch ( LdapInvalidDnException lide )
1432                    {
1433                        throw new NamingException( lide.getMessage() );
1434                    }
1435                }
1436            }
1437    
1438            String msg = I18n.err( I18n.ERR_498, prefix, dn );
1439            throw new NamingException( msg );
1440        }
1441    
1442    
1443        // ------------------------------------------------------------------------
1444        // EventContext implementations
1445        // ------------------------------------------------------------------------
1446    
1447        public void addNamingListener( Name name, int scope, NamingListener namingListener ) throws NamingException
1448        {
1449            ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
1450    
1451            try
1452            {
1453                DirectoryListener listener = new EventListenerAdapter( ( ServerLdapContext ) this, namingListener );
1454                NotificationCriteria criteria = new NotificationCriteria();
1455                criteria.setFilter( filter );
1456                criteria.setScope( SearchScope.getSearchScope( scope ) );
1457                criteria.setAliasDerefMode( AliasDerefMode.getEnum( env ) );
1458                criteria.setBase( buildTarget( DN.fromName( name ) ) );
1459                
1460                service.getEventService().addListener( listener );
1461                listeners.put( namingListener, listener );
1462            }
1463            catch ( Exception e )
1464            {
1465                JndiUtils.wrap( e );
1466            }
1467        }
1468    
1469    
1470        public void addNamingListener( String name, int scope, NamingListener namingListener ) throws NamingException
1471        {
1472            addNamingListener( new LdapName( name ), scope, namingListener );
1473        }
1474    
1475    
1476        public void removeNamingListener( NamingListener namingListener ) throws NamingException
1477        {
1478            try
1479            {
1480                DirectoryListener listener = listeners.remove( namingListener );
1481                
1482                if ( listener != null )
1483                {
1484                    service.getEventService().removeListener( listener );
1485                }
1486            }
1487            catch ( Exception e )
1488            {
1489                JndiUtils.wrap( e );
1490            }
1491        }
1492    
1493    
1494        public boolean targetMustExist() throws NamingException
1495        {
1496            return false;
1497        }
1498    
1499    
1500        /**
1501         * Allows subclasses to register and unregister listeners.
1502         *
1503         * @return the set of listeners used for tracking registered name listeners.
1504         */
1505        protected Map<NamingListener, DirectoryListener> getListeners()
1506        {
1507            return listeners;
1508        }
1509    
1510    
1511        // ------------------------------------------------------------------------
1512        // Utility Methods to Reduce Code
1513        // ------------------------------------------------------------------------
1514    
1515        /**
1516         * Clones this context's DN and adds the components of the name relative to 
1517         * this context to the left hand side of this context's cloned DN. 
1518         * 
1519         * @param relativeName a name relative to this context.
1520         * @return the name of the target
1521         * @throws InvalidNameException if relativeName is not a valid name in
1522         *      the LDAP namespace.
1523         */
1524        DN buildTarget( DN relativeName ) throws NamingException
1525        {
1526            DN target = ( DN ) dn.clone();
1527    
1528            // Add to left hand side of cloned DN the relative name arg
1529            try
1530            {
1531                target.addAllNormalized( target.size(), relativeName );
1532            }
1533            catch (LdapInvalidDnException lide )
1534            {
1535                throw new InvalidNameException( lide.getMessage() );
1536            }
1537            
1538            return target;
1539        }
1540    }