Package org.apache.directory.server.core.jndi

Source Code of org.apache.directory.server.core.jndi.ServerContext

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*    http://www.apache.org/licenses/LICENSE-2.0
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.apache.directory.server.core.jndi;


import java.io.Serializable;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.event.EventContext;
import javax.naming.event.NamingListener;
import javax.naming.ldap.Control;
import javax.naming.spi.DirStateFactory;
import javax.naming.spi.DirectoryManager;

import org.apache.directory.server.core.CoreSession;
import org.apache.directory.server.core.DefaultCoreSession;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.LdapPrincipal;
import org.apache.directory.server.core.OperationManager;
import org.apache.directory.server.core.entry.ClonedServerEntry;
import org.apache.directory.server.core.entry.DefaultServerEntry;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerEntryUtils;
import org.apache.directory.server.core.event.DirectoryListener;
import org.apache.directory.server.core.event.NotificationCriteria;
import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
import org.apache.directory.server.core.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.BindOperationContext;
import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
import org.apache.directory.server.core.interceptor.context.ListOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
import org.apache.directory.server.core.interceptor.context.OperationContext;
import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.ldap.constants.JndiPropertyConstants;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.cursor.EmptyCursor;
import org.apache.directory.shared.ldap.cursor.SingletonCursor;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
import org.apache.directory.shared.ldap.filter.EqualityNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.PresenceNode;
import org.apache.directory.shared.ldap.filter.SearchScope;
import org.apache.directory.shared.ldap.jndi.JndiUtils;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.AVA;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.util.AttributeUtils;
import org.apache.directory.shared.ldap.util.StringTools;


/**
* A non-federated abstract Context implementation.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev: 918819 $
*/
public abstract class ServerContext implements EventContext
{
    /** property key used for deleting the old RDN on a rename */
    public static final String DELETE_OLD_RDN_PROP = JndiPropertyConstants.JNDI_LDAP_DELETE_RDN;

    /** Empty array of controls for use in dealing with them */
    protected static final Control[] EMPTY_CONTROLS = new Control[0];

    /** The directory service which owns this context **/
    private final DirectoryService service;

    /** The cloned environment used by this Context */
    private final Hashtable<String, Object> env;

    /** The distinguished name of this Context */
    private final DN dn;

    /** The set of registered NamingListeners */
    private final Map<NamingListener,DirectoryListener> listeners =
        new HashMap<NamingListener,DirectoryListener>();

    /** The request controls to set on operations before performing them */
    protected Control[] requestControls = EMPTY_CONTROLS;

    /** The response controls to set after performing operations */
    protected Control[] responseControls = EMPTY_CONTROLS;

    /** Connection level controls associated with the session */
    protected Control[] connectControls = EMPTY_CONTROLS;
   
    private final CoreSession session;


    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

   
    /**
     * Must be called by all subclasses to initialize the nexus proxy and the
     * environment settings to be used by this Context implementation.  This
     * specific contstructor relies on the presence of the {@link
     * Context#PROVIDER_URL} key and value to determine the distinguished name
     * of the newly created context.  It also checks to make sure the
     * referenced name actually exists within the system.  This constructor
     * is used for all InitialContext requests.
     *
     * @param service the parent service that manages this context
     * @param env the environment properties used by this context.
     * @throws NamingException if the environment parameters are not set
     * correctly.
     */
    protected ServerContext( DirectoryService service, Hashtable<String, Object> env ) throws Exception
    {
        this.service = service;

        this.env = env;
       
        LdapJndiProperties props = LdapJndiProperties.getLdapJndiProperties( this.env );
        dn = props.getProviderDn();

        /*
         * Need do bind operation here, and opContext returned contains the
         * newly created session.
         */
        BindOperationContext opContext = doBindOperation( props.getBindDn(), props.getCredentials(),
            props.getSaslMechanism(), props.getSaslAuthId() );

        session = opContext.getSession();
        OperationManager operationManager = service.getOperationManager();
       
        if ( ! operationManager.hasEntry( new EntryOperationContext( session, dn ) ) )
        {
            throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
        }
    }
   
   
    /**
     * Must be called by all subclasses to initialize the nexus proxy and the
     * environment settings to be used by this Context implementation.  This
     * constructor is used to propagate new contexts from existing contexts.
     *
     * @param principal the directory user principal that is propagated
     * @param dn the distinguished name of this context
     * @param service the directory service core
     * @throws NamingException if there is a problem creating the new context
     */
    public ServerContext( DirectoryService service, LdapPrincipal principal, Name dn ) throws Exception
    {
        this.service = service;
        this.dn = ( DN ) dn.clone();

        this.env = new Hashtable<String, Object>();
        this.env.put( PROVIDER_URL, dn.toString() );
        this.env.put( DirectoryService.JNDI_KEY, service );
        session = new DefaultCoreSession( principal, service );
        OperationManager operationManager = service.getOperationManager();
       
        if ( ! operationManager.hasEntry( new EntryOperationContext( session, ( DN ) dn ) ) )
        {
            throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
        }
    }


    public ServerContext( DirectoryService service, CoreSession session, Name dn ) throws Exception
    {
        this.service = service;
        this.dn = ( DN ) dn.clone();
        this.env = new Hashtable<String, Object>();
        this.env.put( PROVIDER_URL, dn.toString() );
        this.env.put( DirectoryService.JNDI_KEY, service );
        this.session = session;
        OperationManager operationManager = service.getOperationManager();
       
        if ( ! operationManager.hasEntry( new EntryOperationContext( session, ( DN ) dn ) ) )
        {
            throw new NameNotFoundException( I18n.err( I18n.ERR_490, dn ) );
        }
    }


    /**
     * Set the referral handling flag into the operation context using
     * the JNDI value stored into the environment.
     */
    protected void injectReferralControl( OperationContext opCtx )
    {
        if ( "ignore".equalsIgnoreCase( (String)env.get( Context.REFERRAL ) ) )
        {
            opCtx.ignoreReferral();
        }
        else if ( "throw".equalsIgnoreCase( (String)env.get( Context.REFERRAL ) ) )
        {
            opCtx.throwReferral();
        }
        else
        {
            // TODO : handle the 'follow' referral option
            opCtx.throwReferral();
        }
    }
    // ------------------------------------------------------------------------
    // Protected Methods for Operations
    // ------------------------------------------------------------------------
    // Use these methods instead of manually calling the nexusProxy so we can
    // add request controls to operation contexts before the call and extract
    // response controls from the contexts after the call.  NOTE that the
    // JndiUtils.fromJndiControls( requestControls ) must be cleared after each operation.  This makes a
    // context not thread safe.
    // ------------------------------------------------------------------------

    /**
     * Used to encapsulate [de]marshalling of controls before and after add operations.
     * @param entry
     * @param target
     */
    protected void doAddOperation( DN target, ServerEntry entry ) throws Exception
    {
        // setup the op context and populate with request controls
        AddOperationContext opCtx = new AddOperationContext( session, entry );

        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
       
        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );
       
        // execute add operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.add( opCtx );
   
        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after delete operations.
     * @param target
     */
    protected void doDeleteOperation( DN target ) throws Exception
    {
        // setup the op context and populate with request controls
        DeleteOperationContext opCtx = new DeleteOperationContext( session, target );

        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );

        // execute delete operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.delete( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after list operations.
     * @param dn
     * @param aliasDerefMode
     * @param filter
     * @param searchControls
     * @return NamingEnumeration
     */
    protected EntryFilteringCursor doSearchOperation( DN dn, AliasDerefMode aliasDerefMode,
        ExprNode filter, SearchControls searchControls ) throws Exception
    {
        OperationManager operationManager = service.getOperationManager();
        EntryFilteringCursor results = null;
        OperationContext opContext;
       
        Object typesOnlyObj = getEnvironment().get( "java.naming.ldap.typesOnly" );
        boolean typesOnly = false;
        if( typesOnlyObj != null )
        {
            typesOnly = Boolean.parseBoolean( typesOnlyObj.toString() );
        }

        // We have to check if it's a compare operation or a search.
        // A compare operation has a OBJECT scope search, the filter must
        // be of the form (object=value) (no wildcards), and no attributes
        // should be asked to be returned.
        if ( ( searchControls.getSearchScope() == SearchControls.OBJECT_SCOPE )
            && ( ( searchControls.getReturningAttributes() != null )
                && ( searchControls.getReturningAttributes().length == 0 ) )
            && ( filter instanceof EqualityNode ) )
        {
            opContext = new CompareOperationContext( session, dn, ((EqualityNode)filter).getAttribute(), ((EqualityNode)filter).getValue() );
           
            // Inject the referral handling into the operation context
            injectReferralControl( opContext );

            // Call the operation
            boolean result = operationManager.compare( (CompareOperationContext)opContext );

            // setup the op context and populate with request controls
            opContext = new SearchOperationContext( session, dn, filter,
                searchControls );
            ((SearchOperationContext)opContext).setAliasDerefMode( aliasDerefMode );
            opContext.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
           
            ( ( SearchOperationContext ) opContext ).setTypesOnlytypesOnly );
           
            if ( result )
            {
                ServerEntry emptyEntry = new DefaultServerEntry( service.getSchemaManager(), DN.EMPTY_DN );
                return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( emptyEntry ), (SearchOperationContext)opContext );
            }
            else
            {
                return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), (SearchOperationContext)opContext );
            }
        }
        else
        {
            // It's a Search
           
            // setup the op context and populate with request controls
            opContext = new SearchOperationContext( session, dn, filter, searchControls );
            ( ( SearchOperationContext ) opContext ).setAliasDerefMode( aliasDerefMode );
            opContext.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
            ( ( SearchOperationContext ) opContext ).setTypesOnlytypesOnly );
           
            // Inject the referral handling into the operation context
            injectReferralControl( opContext );

            // execute search operation
            results = operationManager.search( (SearchOperationContext)opContext );
        }

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opContext.getResponseControls() );

        return results;
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after list operations.
     */
    protected EntryFilteringCursor doListOperation( DN target ) throws Exception
    {
        // setup the op context and populate with request controls
        ListOperationContext opCtx = new ListOperationContext( session, target );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // execute list operation
        OperationManager operationManager = service.getOperationManager();
        EntryFilteringCursor results = operationManager.list( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );

        return results;
    }


    protected ServerEntry doGetRootDSEOperation( DN target ) throws Exception
    {
        GetRootDSEOperationContext opCtx = new GetRootDSEOperationContext( session, target );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // do not reset request controls since this is not an external
        // operation and not do bother setting the response controls either
        OperationManager operationManager = service.getOperationManager();
        return operationManager.getRootDSE( opCtx );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after lookup operations.
     */
    protected ServerEntry doLookupOperation( DN target ) throws Exception
    {
        // setup the op context and populate with request controls
        LookupOperationContext opCtx;

        // execute lookup/getRootDSE operation
        opCtx = new LookupOperationContext( session, target );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
        OperationManager operationManager = service.getOperationManager();
        ServerEntry serverEntry = operationManager.lookup( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
        return serverEntry;
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after lookup operations.
     */
    protected ServerEntry doLookupOperation( DN target, String[] attrIds ) throws Exception
    {
        // setup the op context and populate with request controls
        LookupOperationContext opCtx;

        // execute lookup/getRootDSE operation
        opCtx = new LookupOperationContext( session, target, attrIds );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );
        OperationManager operationManager = service.getOperationManager();
        ClonedServerEntry serverEntry = operationManager.lookup( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );

        // Now remove the ObjectClass attribute if it has not been requested
        if ( ( opCtx.getAttrsId() != null ) && ( opCtx.getAttrsId().size() != 0 ) )
        {
            if ( ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ) != null )
                && ( serverEntry.get( SchemaConstants.OBJECT_CLASS_AT ).size() == 0 ) )
            {
                serverEntry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
            }
        }

        return serverEntry;
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after bind operations.
     */
    protected BindOperationContext doBindOperation( DN bindDn, byte[] credentials, String saslMechanism,
        String saslAuthId ) throws Exception
    {
        // setup the op context and populate with request controls
        BindOperationContext opCtx = new BindOperationContext( null );
        opCtx.setDn( bindDn );
        opCtx.setCredentials( credentials );
        opCtx.setSaslMechanism( saslMechanism );
        opCtx.setSaslAuthId( saslAuthId );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // execute bind operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.bind( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
        return opCtx;
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
     */
    protected void doMoveAndRenameOperation( DN oldDn, DN parent, RDN newRdn, boolean delOldDn )
        throws Exception
    {
        // setup the op context and populate with request controls
        MoveAndRenameOperationContext opCtx = new MoveAndRenameOperationContext( session, oldDn, parent, new RDN(
            newRdn ), delOldDn );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );
       
        // execute moveAndRename operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.moveAndRename( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after modify operations.
     */
    protected void doModifyOperation( DN dn, List<Modification> modifications ) throws Exception
    {
        // setup the op context and populate with request controls
        ModifyOperationContext opCtx = new ModifyOperationContext( session, dn, modifications );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );
       
        // execute modify operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.modify( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after moveAndRename operations.
     */
    protected void doMove( DN oldDn, DN target ) throws Exception
    {
        // setup the op context and populate with request controls
        MoveOperationContext opCtx = new MoveOperationContext( session, oldDn, target );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );
       
        // execute move operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.move( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }


    /**
     * Used to encapsulate [de]marshalling of controls before and after rename operations.
     */
    protected void doRename( DN oldDn, RDN newRdn, boolean delOldRdn ) throws Exception
    {
        // setup the op context and populate with request controls
        RenameOperationContext opCtx = new RenameOperationContext( session, oldDn, newRdn, delOldRdn );
        opCtx.addRequestControls( JndiUtils.fromJndiControls( requestControls ) );

        // Inject the referral handling into the operation context
        injectReferralControl( opCtx );
       
        // execute rename operation
        OperationManager operationManager = service.getOperationManager();
        operationManager.rename( opCtx );

        // clear the request controls and set the response controls
        requestControls = EMPTY_CONTROLS;
        responseControls = JndiUtils.toJndiControls( opCtx.getResponseControls() );
    }

   
    public CoreSession getSession()
    {
        return session;
    }
   
   
    public DirectoryService getDirectoryService()
    {
        return service;
    }
   
   
    // ------------------------------------------------------------------------
    // New Impl Specific Public Methods
    // ------------------------------------------------------------------------

    /**
     * Gets a handle on the root context of the DIT.  The RootDSE as the present user.
     *
     * @return the rootDSE context
     * @throws NamingException if this fails
     */
    public abstract ServerContext getRootContext() throws NamingException;


    /**
     * Gets the {@link DirectoryService} associated with this context.
     *
     * @return the directory service associated with this context
     */
    public DirectoryService getService()
    {
        return service;
    }


    // ------------------------------------------------------------------------
    // Protected Accessor Methods
    // ------------------------------------------------------------------------

   
    /**
     * Gets the distinguished name of the entry associated with this Context.
     *
     * @return the distinguished name of this Context's entry.
     */
    protected Name getDn()
    {
        return dn;
    }


    // ------------------------------------------------------------------------
    // JNDI Context Interface Methods
    // ------------------------------------------------------------------------

    /**
     * @see javax.naming.Context#close()
     */
    public void close() throws NamingException
    {
        for ( DirectoryListener listener : listeners.values() )
        {
            try
            {
                service.getEventService().removeListener( listener );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
        }
       
        listeners.clear();
    }


    /**
     * @see javax.naming.Context#getNameInNamespace()
     */
    public String getNameInNamespace() throws NamingException
    {
        return dn.getName();
    }


    /**
     * @see javax.naming.Context#getEnvironment()
     */
    public Hashtable<String, Object> getEnvironment()
    {
        return env;
    }


    /**
     * @see javax.naming.Context#addToEnvironment(java.lang.String,
     * java.lang.Object)
     */
    public Object addToEnvironment( String propName, Object propVal ) throws NamingException
    {
        return env.put( propName, propVal );
    }


    /**
     * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
     */
    public Object removeFromEnvironment( String propName ) throws NamingException
    {
        return env.remove( propName );
    }


    /**
     * @see javax.naming.Context#createSubcontext(java.lang.String)
     */
    public Context createSubcontext( String name ) throws NamingException
    {
        return createSubcontext( new DN( name ) );
    }


    /**
     * @see javax.naming.Context#createSubcontext(javax.naming.Name)
     */
    public Context createSubcontext( Name name ) throws NamingException
    {
        DN target = buildTarget( name );
        ServerEntry serverEntry = service.newEntry( target );
        serverEntry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, JavaLdapSupport.JCONTAINER_ATTR );

        // Now add the CN attribute, which is mandatory
        RDN rdn = target.getRdn();

        if ( rdn != null )
        {
            if ( SchemaConstants.CN_AT.equals( rdn.getNormType() ) )
            {
                serverEntry.put( rdn.getUpType(), ( String ) rdn.getUpValue() );
            }
            else
            {
                // No CN in the rdn, this is an error
                throw new LdapSchemaViolationException( I18n.err( I18n.ERR_491, name), ResultCodeEnum.OBJECT_CLASS_VIOLATION );
            }
        }
        else
        {
            // No CN in the rdn, this is an error
            throw new LdapSchemaViolationException( I18n.err( I18n.ERR_491, name), ResultCodeEnum.OBJECT_CLASS_VIOLATION );
        }

        /*
         * Add the new context to the server which as a side effect adds
         * operational attributes to the serverEntry refering instance which
         * can them be used to initialize a new ServerLdapContext.  Remember
         * we need to copy over the controls as well to propagate the complete
         * environment besides what's in the hashtable for env.
         */
        try
        {
            doAddOperation( target, serverEntry );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
       
        ServerLdapContext ctx = null;
       
        try
        {
            ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), target );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
       
        return ctx;
    }


    /**
     * @see javax.naming.Context#destroySubcontext(java.lang.String)
     */
    public void destroySubcontext( String name ) throws NamingException
    {
        destroySubcontext( new DN( name ) );
    }


    /**
     * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
     */
    public void destroySubcontext( Name name ) throws NamingException
    {
        DN target = buildTarget( name );

        if ( target.size() == 0 )
        {
            throw new LdapNoPermissionException( I18n.err( I18n.ERR_492 ) );
        }

        try
        {
            doDeleteOperation( target );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
    }


    /**
     * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
     */
    public void bind( String name, Object obj ) throws NamingException
    {
        bind( new DN( name ), obj );
    }


    private void injectRdnAttributeValues( DN target, ServerEntry serverEntry ) throws NamingException
    {
        // Add all the RDN attributes and their values to this entry
        RDN rdn = target.getRdn( target.size() - 1 );

        if ( rdn.size() == 1 )
        {
            serverEntry.put( rdn.getUpType(), ( String ) rdn.getNormValue() );
        }
        else
        {
            for ( AVA atav : rdn )
            {
                serverEntry.put( atav.getUpType(), atav.getNormValue().getString() );
            }
        }
    }


    /**
     * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
     */
    public void bind( Name name, Object obj ) throws NamingException
    {
        // First, use state factories to do a transformation
        DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );

        DN target = buildTarget( name );

        // let's be sure that the Attributes is case insensitive
        ServerEntry outServerEntry = ServerEntryUtils.toServerEntry( AttributeUtils.toCaseInsensitive( res
            .getAttributes() ), target, service.getSchemaManager() );

        if ( outServerEntry != null )
        {
            try
            {
                doAddOperation( target, outServerEntry );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
            return;
        }

        if ( obj instanceof ServerEntry )
        {
            try
            {
                doAddOperation( target, ( ServerEntry ) obj );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
        }
        // Check for Referenceable
        else if ( obj instanceof Referenceable )
        {
            throw new NamingException( I18n.err( I18n.ERR_493 ) );
        }
        // Store different formats
        else if ( obj instanceof Reference )
        {
            // Store as ref and add outAttrs
            throw new NamingException( I18n.err( I18n.ERR_494 ) );
        }
        else if ( obj instanceof Serializable )
        {
            // Serialize and add outAttrs
            ServerEntry serverEntry = service.newEntry( target );

            if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
            {
                for ( EntryAttribute serverAttribute : outServerEntry )
                {
                    serverEntry.put( serverAttribute );
                }
            }

            // Get target and inject all rdn attributes into entry
            injectRdnAttributeValues( target, serverEntry );

            // Serialize object into entry attributes and add it.
            JavaLdapSupport.serialize( serverEntry, obj, service.getSchemaManager() );
            try
            {
                doAddOperation( target, serverEntry );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
        }
        else if ( obj instanceof DirContext )
        {
            // Grab attributes and merge with outAttrs
            ServerEntry serverEntry = ServerEntryUtils.toServerEntry( ( ( DirContext ) obj ).getAttributes( "" ),
                target, service.getSchemaManager() );

            if ( ( outServerEntry != null ) && ( outServerEntry.size() > 0 ) )
            {
                for ( EntryAttribute serverAttribute : outServerEntry )
                {
                    serverEntry.put( serverAttribute );
                }
            }

            injectRdnAttributeValues( target, serverEntry );
            try
            {
                doAddOperation( target, serverEntry );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
        }
        else
        {
            throw new NamingException( I18n.err( I18n.ERR_495, obj ) );
        }
    }


    /**
     * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
     */
    public void rename( String oldName, String newName ) throws NamingException
    {
        rename( new DN( oldName ), new DN( newName ) );
    }


    /**
     * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
     */
    public void rename( Name oldName, Name newName ) throws NamingException
    {
        DN oldDn = buildTarget( oldName );
        DN newDn = buildTarget( newName );

        if ( oldDn.size() == 0 )
        {
            throw new LdapNoPermissionException( I18n.err( I18n.ERR_312 ) );
        }

        // calculate parents
        DN oldParent = (DN)oldDn.clone();
        oldParent.remove( oldDn.size() - 1 );
        DN newParent = ( DN ) newDn.clone();
        newParent.remove( newDn.size() - 1 );

        RDN oldRdn = oldDn.getRdn();
        RDN newRdn = newDn.getRdn();
        boolean delOldRdn = true;

        /*
         * Attempt to use the java.naming.ldap.deleteRDN environment property
         * to get an override for the deleteOldRdn option to modifyRdn. 
         */
        if ( null != env.get( DELETE_OLD_RDN_PROP ) )
        {
            String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
            delOldRdn = !delOldRdnStr.equalsIgnoreCase( "false" ) && !delOldRdnStr.equalsIgnoreCase( "no" )
                && !delOldRdnStr.equals( "0" );
        }

        /*
         * We need to determine if this rename operation corresponds to a simple
         * RDN name change or a move operation.  If the two names are the same
         * except for the RDN then it is a simple modifyRdn operation.  If the
         * names differ in size or have a different baseDN then the operation is
         * a move operation.  Furthermore if the RDN in the move operation
         * changes it is both an RDN change and a move operation.
         */
        if ( oldParent.equals( newParent ) )
        {
            try
            {
                doRename( oldDn, newRdn, delOldRdn );
            }
            catch ( Exception e )
            {
                JndiUtils.wrap( e );
            }
        }
        else
        {
            if ( newRdn.equals( oldRdn ) )
            {
                try
                {
                    doMove( oldDn, newParent );
                }
                catch ( Exception e )
                {
                    JndiUtils.wrap( e );
                }
            }
            else
            {
                try
                {
                    doMoveAndRenameOperation( oldDn, newParent, newRdn, delOldRdn );
                }
                catch ( Exception e )
                {
                    JndiUtils.wrap( e );
                }
            }
        }
    }


    /**
     * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
     */
    public void rebind( String name, Object obj ) throws NamingException
    {
        rebind( new DN( name ), obj );
    }


    /**
     * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
     */
    public void rebind( Name name, Object obj ) throws NamingException
    {
        DN target = buildTarget( name );
        OperationManager operationManager = service.getOperationManager();
       
        try
        {
            if ( operationManager.hasEntry( new EntryOperationContext( session, target ) ) )
            {
                doDeleteOperation( target );
            }
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }

        bind( name, obj );
    }


    /**
     * @see javax.naming.Context#unbind(java.lang.String)
     */
    public void unbind( String name ) throws NamingException
    {
        unbind( new DN( name ) );
    }


    /**
     * @see javax.naming.Context#unbind(javax.naming.Name)
     */
    public void unbind( Name name ) throws NamingException
    {
        try
        {
            doDeleteOperation( buildTarget( name ) );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
    }


    /**
     * @see javax.naming.Context#lookup(java.lang.String)
     */
    public Object lookup( String name ) throws NamingException
    {
        if ( StringTools.isEmpty( name ) )
        {
            return lookup( DN.EMPTY_DN );
        }
        else
        {
            return lookup( new DN( name ) );
        }
    }


    /**
     * @see javax.naming.Context#lookup(javax.naming.Name)
     */
    public Object lookup( Name name ) throws NamingException
    {
        Object obj;
        DN target = buildTarget( name );

        ServerEntry serverEntry = null;

        try
        {
            if ( name.size() == 0 )
            {
                serverEntry = doGetRootDSEOperation( target );
            }
            else
            {
                serverEntry = doLookupOperation( target );
            }
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }

        try
        {
            obj = DirectoryManager.getObjectInstance( null, name, this, env,
                ServerEntryUtils.toBasicAttributes( serverEntry ) );
        }
        catch ( Exception e )
        {
            String msg = I18n.err( I18n.ERR_497, target );
            NamingException ne = new NamingException( msg );
            ne.setRootCause( e );
            throw ne;
        }

        if ( obj != null )
        {
            return obj;
        }

        // First lets test and see if the entry is a serialized java object
        if ( serverEntry.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
        {
            // Give back serialized object and not a context
            return JavaLdapSupport.deserialize( serverEntry );
        }

        // Initialize and return a context since the entry is not a java object
        ServerLdapContext ctx = null;
       
        try
        {
            ctx = new ServerLdapContext( service, session.getEffectivePrincipal(), target );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
       
        return ctx;
    }


    /**
     * @see javax.naming.Context#lookupLink(java.lang.String)
     */
    public Object lookupLink( String name ) throws NamingException
    {
        throw new UnsupportedOperationException();
    }


    /**
     * @see javax.naming.Context#lookupLink(javax.naming.Name)
     */
    public Object lookupLink( Name name ) throws NamingException
    {
        throw new UnsupportedOperationException();
    }


    /**
     * Non-federated implementation presuming the name argument is not a
     * composite name spanning multiple namespaces but a compound name in
     * the same LDAP namespace.  Hence the parser returned is always the
     * same as calling this method with the empty String.
     *
     * @see javax.naming.Context#getNameParser(java.lang.String)
     */
    public NameParser getNameParser( String name ) throws NamingException
    {
        return new NameParser()
        {
            public Name parse( String name ) throws NamingException
            {
                return new DN( name );
            }
        };
    }


    /**
     * Non-federated implementation presuming the name argument is not a
     * composite name spanning multiple namespaces but a compound name in
     * the same LDAP namespace.  Hence the parser returned is always the
     * same as calling this method with the empty String Name.
     *
     * @see javax.naming.Context#getNameParser(javax.naming.Name)
     */
    public NameParser getNameParser( Name name ) throws NamingException
    {
        return new NameParser()
        {
            public Name parse( String name ) throws NamingException
            {
                return new DN( name );
            }
        };
    }


    /**
     * @see javax.naming.Context#list(java.lang.String)
     */
    @SuppressWarnings(value =
        { "unchecked" })
    public NamingEnumeration list( String name ) throws NamingException
    {
        return list( new DN( name ) );
    }


    /**
     * @see javax.naming.Context#list(javax.naming.Name)
     */
    @SuppressWarnings(value =
        { "unchecked" })
    public NamingEnumeration list( Name name ) throws NamingException
    {
        try
        {
            return new NamingEnumerationAdapter( doListOperation( buildTarget( name ) ) );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
            return null; // shut up compiler
        }
    }


    /**
     * @see javax.naming.Context#listBindings(java.lang.String)
     */
    @SuppressWarnings(value =
        { "unchecked" })
    public NamingEnumeration listBindings( String name ) throws NamingException
    {
        return listBindings( new DN( name ) );
    }


    /**
     * @see javax.naming.Context#listBindings(javax.naming.Name)
     */
    @SuppressWarnings(value =
        { "unchecked" })
    public NamingEnumeration listBindings( Name name ) throws NamingException
    {
        // Conduct a special one level search at base for all objects
        DN base = buildTarget( name );
        PresenceNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
        SearchControls ctls = new SearchControls();
        ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
        AliasDerefMode aliasDerefMode = AliasDerefMode.getEnum( getEnvironment() );
        try
        {
            return new NamingEnumerationAdapter( doSearchOperation( base, aliasDerefMode, filter, ctls ) );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
            return null; // shutup compiler
        }
    }


    /**
     * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
     */
    public String composeName( String name, String prefix ) throws NamingException
    {
        return composeName( new DN( name ), new DN( prefix ) ).toString();
    }


    /**
     * @see javax.naming.Context#composeName(javax.naming.Name,
     * javax.naming.Name)
     */
    public Name composeName( Name name, Name prefix ) throws NamingException
    {
        // No prefix reduces to name, or the name relative to this context
        if ( prefix == null || prefix.size() == 0 )
        {
            return name;
        }

        /*
         * Example: This context is ou=people and say name is the relative
         * name of uid=jwalker and the prefix is dc=domain.  Then we must
         * compose the name relative to prefix which would be:
         *
         * uid=jwalker,ou=people,dc=domain.
         *
         * The following general algorithm generates the right name:
         *      1). Find the Dn for name and walk it from the head to tail
         *          trying to match for the head of prefix.
         *      2). Remove name components from the Dn until a match for the
         *          head of the prefix is found.
         *      3). Return the remainder of the fqn or Dn after chewing off some
         */

        // 1). Find the Dn for name and walk it from the head to tail
        Name fqn = buildTarget( name );
        String head = prefix.get( 0 );

        // 2). Walk the fqn trying to match for the head of the prefix
        while ( fqn.size() > 0 )
        {
            // match found end loop
            if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
            {
                return fqn;
            }
            else
            // 2). Remove name components from the Dn until a match
            {
                fqn.remove( 0 );
            }
        }

        String msg = I18n.err( I18n.ERR_498, prefix, dn );
        throw new NamingException( msg );
    }


    // ------------------------------------------------------------------------
    // EventContext implementations
    // ------------------------------------------------------------------------

    public void addNamingListener( Name name, int scope, NamingListener namingListener ) throws NamingException
    {
        ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );

        try
        {
            DirectoryListener listener = new EventListenerAdapter( ( ServerLdapContext ) this, namingListener );
            NotificationCriteria criteria = new NotificationCriteria();
            criteria.setFilter( filter );
            criteria.setScope( SearchScope.getSearchScope( scope ) );
            criteria.setAliasDerefMode( AliasDerefMode.getEnum( env ) );
            criteria.setBase( buildTarget( name ) );
           
            service.getEventService().addListener( listener );
            listeners.put( namingListener, listener );
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
    }


    public void addNamingListener( String name, int scope, NamingListener namingListener ) throws NamingException
    {
        addNamingListener( new DN( name ), scope, namingListener );
    }


    public void removeNamingListener( NamingListener namingListener ) throws NamingException
    {
        try
        {
            DirectoryListener listener = listeners.remove( namingListener );
           
            if ( listener != null )
            {
                service.getEventService().removeListener( listener );
            }
        }
        catch ( Exception e )
        {
            JndiUtils.wrap( e );
        }
    }


    public boolean targetMustExist() throws NamingException
    {
        return false;
    }


    /**
     * Allows subclasses to register and unregister listeners.
     *
     * @return the set of listeners used for tracking registered name listeners.
     */
    protected Map<NamingListener, DirectoryListener> getListeners()
    {
        return listeners;
    }


    // ------------------------------------------------------------------------
    // Utility Methods to Reduce Code
    // ------------------------------------------------------------------------

    /**
     * Clones this context's DN and adds the components of the name relative to
     * this context to the left hand side of this context's cloned DN.
     *
     * @param relativeName a name relative to this context.
     * @return the name of the target
     * @throws InvalidNameException if relativeName is not a valid name in
     *      the LDAP namespace.
     */
    DN buildTarget( Name relativeName ) throws InvalidNameException
    {
        DN target = ( DN ) dn.clone();

        // Add to left hand side of cloned DN the relative name arg
        target.addAllNormalized( target.size(), relativeName );
        return target;
    }
}
TOP

Related Classes of org.apache.directory.server.core.jndi.ServerContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.