/**
 *
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.naming;

import java.util.Hashtable;

import javax.ejb.spi.HandleDelegate;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.omg.CORBA.ORB;

import org.objectweb.carol.jndi.ns.JacORBCosNaming;

import org.objectweb.jonas_ejb.container.JHandleDelegate;

import org.objectweb.jonas_lib.naming.ContainerNaming;

import org.objectweb.jonas.common.Log;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * Naming Manager for an EJB Server. this singleton class must exist in each
 * jonas server.
 * @author Philippe Durieux
 * @author Philippe Coq : Monolog
 * @author Florent Benoit & Ludovic Bert : Context for web container
 */
public class NamingManager implements ContainerNaming {

    /**
     * Logger used for traces
     */
    private static Logger logger = Log.getLogger(Log.JONAS_NAMING_PREFIX);

    /**
     * Naming Context associated with the thread
     */
    private ThreadLocal threadContext = new ThreadLocal();

    /**
     * Initial Context
     */
    private InitialContext ictx = null;

    /**
     * Context of the server
     */
    private static Context serverContext = null;

    /**
     * Environment
     */
    private Hashtable myEnv = null;

    /**
     * Associate a context to a class loader
     */
    private Hashtable clBindings = null;

    /**
     * Static context used by client container. One context for all the JVM.
     */
    private static Context clientCtx = null;

    /**
     * Singleton management: - the constructor is private. - use static method
     * getInstance to retrieve/create the instance.
     */
    private static NamingManager unique = null;

    /**
     * UserTransaction object, to be shared by all components.
     */
    private Object userTransaction = null;

    /**
     * HandleDelegate implementation is shared by all component contexts
     */
    private HandleDelegate handleDelegate = null;

    /**
     * Create the naming manager.
     * @throws NamingException if no initial context is built
     */
    private NamingManager() throws NamingException {
        try {
            ictx = new InitialContext();
            // there is a bug in carol that makes this not work.
            //myEnv = ictx.getEnvironment();
            clBindings = new Hashtable();

        } catch (NamingException n) {
            logger.log(BasicLevel.ERROR, "NamingManager: " + n.getExplanation());
            Throwable t = n.getRootCause();
            if (t != null) {
                if (t.getMessage().startsWith("Connection refused to host:")) {
                    logger.log(BasicLevel.ERROR, "NamingManager: rmi registry not started ?");
                } else if (t.getMessage().startsWith("error during remote invocation")) {
                    logger.log(BasicLevel.ERROR, "NamingManager: jrmi registry not started ?");
                } else {
                    logger.log(BasicLevel.ERROR, "NamingManager: " + t.getMessage());
                }
            }
            throw n;
        }

    }

    /**
     * Return the unique instance of a NamingManager.
     * @return NamingManager the unique instance.
     * @throws NamingException if it failed.
     */
    public static NamingManager getInstance() throws NamingException {
        if (unique == null) {
            unique = new NamingManager();
        }
        return unique;
    }

    // ------------------------------------------------------------------
    // ContainerNaming implementation
    // ------------------------------------------------------------------

    /**
     * Get the initialContext used in this jonas server.
     * @return InitialContext the initial context.
     */
    public InitialContext getInitialContext() {
        return ictx;
    }

    /**
     * @return The HandleDelegate implementation for this server
     */
    public HandleDelegate getHandleDelegate() {
        if (handleDelegate == null) {
            handleDelegate = new JHandleDelegate();
        }
        return handleDelegate;
    }

    /**
     * Create Context for application and component environments. (formally
     * known as createComponentContext)
     * @param namespace namespace to used for the Context
     * @return a java: context with comp/ subcontext
     * @throws NamingException if the creation of the java: context failed.
     */
    public Context createEnvironmentContext(String namespace) throws NamingException {

        if (Log.isDebugNaming()) {
            logger.log(BasicLevel.DEBUG, namespace);
        }

        // Create a new environment
        CompNamingContext ctx = new CompNamingContext(namespace);

        // Create subContext
        Context compCtx = ctx.createSubcontext("comp");

        // Bind java:comp/UserTransaction
        if (userTransaction == null) {
            try {
                userTransaction = ictx.lookup("javax.transaction.UserTransaction");
            } catch (NamingException ne) {
                if (Log.isDebugNaming()) {
                    logger.log(BasicLevel.DEBUG, "Cannot lookup UserTransaction.");
                }
            }
        }
        if (userTransaction != null) {
            compCtx.rebind("UserTransaction", userTransaction);
        }

        ORB orb = JacORBCosNaming.getOrb();
        if (orb != null) {
            try {
                // Bind the java:comp/ORB object instance
                compCtx.rebind("ORB", orb);
            } catch (NamingException e) {
                String err = "Cannot create URL for java:comp/ORB object : " + e;
                logger.log(BasicLevel.ERROR, err);
                throw new NamingException(err);
            }
        }

        // Bind java:comp/HandleDelegate
        compCtx.rebind("HandleDelegate", getHandleDelegate());

        return ctx;
    }

    /**
     * Get the Context associated with the current thread or to a class loader
     * @return Context the component context.
     * @throws NamingException When operation is not allowed
     */
    public Context getComponentContext() throws NamingException {

        Context ctx = null;

        // Check if there is a context to the local thread
        // For ejbs
        ctx = (Context) threadContext.get();
        if (ctx != null) {
            if (Log.isDebugNaming()) {
                logger.log(BasicLevel.DEBUG, "return Context for ejb");
            }
            return ctx;
        }

        // Check if there is a context which match the currentThread
        // classLoader
        // For webapps
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if ((cl != null) && (cl.getParent() != null)) {
            ctx = (Context) clBindings.get(cl.getParent());
            if (ctx != null) {
                if (Log.isDebugNaming()) {
                    logger.log(BasicLevel.DEBUG, "return Context for webapp");
                }
                return ctx;
            }
        }

        // Check static context. use in client. One context per JVM.
        if (clientCtx != null) {
            ctx = clientCtx;
            if (ctx != null) {
                if (Log.isDebugNaming()) {
                    logger.log(BasicLevel.DEBUG, "return Context for client");
                }
                return ctx;
            }
        }

        // No context found. This is outside of a j2ee component or server
        // component.
        if (ctx == null) {
            ctx = getServerContext();
            if (Log.isDebugNaming()) {
                logger.log(BasicLevel.DEBUG, "return default server Context");
            }
        }
        return ctx;
    }

    /**
     * Associate this CompNamingContext with the current thread. This method
     * should be called in preinvoke/postinvoke and when we build the bean
     * environment or web environment.
     * @param ctx the context to associate to the current thread.
     * @return Context the context of the thread
     */
    public Context setComponentContext(Context ctx) {
        Context ret = (Context) threadContext.get();
        threadContext.set(ctx);
        return ret;
    }

    /**
     * Set back the context with the given value.
     * Don't return the previous context, use setComponentContext() method for this.
     * @param ctx the context to associate to the current thread.
     */
    public void resetComponentContext(Context ctx) {
        threadContext.set(ctx);
    }


    /**
     * Associate the specified CompNamingContext with the given classloader.
     * @param ctx the context to associate to the classloader.
     * @param cl the classloader which is bind to the context.
     */
    public void setComponentContext(Context ctx, ClassLoader cl) {
        if (Log.isDebugNaming()) {
            logger.log(BasicLevel.DEBUG, "class loader = " + cl);
        }
        clBindings.put(cl, ctx);
    }

    /**
     * Set the context used by client container (per JVM instead of per thread)
     * @param ctx the context to set
     */
    public void setClientContainerComponentContext(Context ctx) {
        if (Log.isDebugNaming()) {
            logger.log(BasicLevel.DEBUG, "");
        }
        clientCtx = ctx;
    }

    /**
     * Return the CompNamingContext associated with the given classloader.
     * @param cl the classloader which is bind to the context.
     * @return the CompNamingContext associated with the given classloader.
     */
    public Context getComponentContext(ClassLoader cl) {
        if (Log.isDebugNaming()) {
            logger.log(BasicLevel.DEBUG, "class loader = " + cl);
        }
        return (Context) clBindings.get(cl);
    }

    /**
     * Remove the CompNamingContext associated with the given classloader.
     * @param cl the classloader which is bind to the context.
     */
    public void unSetComponentContext(ClassLoader cl) {
        if (Log.isDebugNaming()) {
            logger.log(BasicLevel.DEBUG, "class loader = " + cl);
        }
        clBindings.remove(cl);
    }

    /**
     * Return the environment for JNDI This is used only for handles today.
     * @return Hashtable the environment.
     */
    public Hashtable getEnv() {
        return myEnv;
    }

    /**
     * Create Immutable Context. Not implemented !
     * @param namespace namespace to used for the Context
     * @return Context an immutable context
     * @throws NamingException if the creation of the java: context failed.
     */
    public Context createImmutableEnvironmentContext(String namespace) throws NamingException {
        logger.log(BasicLevel.ERROR, "Not Implemented");
        return null;
    }

    // ------------------------------------------------------------------
    // other proprietary methods
    // ------------------------------------------------------------------

    /**
     * Get the server component context. This is used only internally in the
     * jonas NamingManager.
     * @return Context the server component context.
     */
    public Context getServerContext() {
        if (serverContext == null) {
            try {
                serverContext = createEnvironmentContext("server");
            } catch (NamingException e) {
                logger.log(BasicLevel.ERROR, "cannot create serverContext:" + e);
             }
        }
        return serverContext;
    }

}