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