/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.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:SingletonNamingManager.java 10360 2007-05-14 11:39:21Z durieuxp $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.naming;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.loader.OSGiClassLoader;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JNamingManager;

/**
 * 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 SingletonNamingManager implements JNamingManager {

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

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

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

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

    /**
     * Environment.
     * TODO This field is ALWAYS null !
     */
    private Hashtable<?, ?> myEnv = null;

    /**
     * Associate a context to a class loader.
     */
    private Hashtable<ClassLoader, Context> clBindings = new Hashtable<ClassLoader, Context>();

    /**
     * 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 JNamingManager unique = null;

    /**
     * Unique instance of the JComponentContextFactory.
     * This attribute must just be set after of the NamingManager creation.
     */
    private JComponentContextFactory factory = null;

    /**
     * Create the naming manager.
     * @throws NamingException if no initial context is built
     */
    private SingletonNamingManager() throws NamingException {

        // Get InitialContext with the correct class loader
        // We need to access the carol classes here.
        IExecution<InitialContext> ie = new IExecution<InitialContext>() {
            public InitialContext execute() throws Exception {
                return new InitialContext();
            }
        };
        ExecutionResult<InitialContext> result = null;
        result = RunnableHelper.execute(new OSGiClassLoader(), ie);
        if (result.hasException()) {
            logger.log(BasicLevel.ERROR, "NamingManager: ", result.getException());
            throw new NamingException("Cannot get InitialContext: " + result.getException());
        }
        ictx = result.getResult();

        // We must instantiate the unique instance
        // in order to retrieve the same object in traditional or OSGi context
        unique = this;
    }

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

    /**
     * Set the unique instance of the JComponentContextFactory.
     * @param factory the component context factory
     */
    public void setJComponentContextFactory(final JComponentContextFactory factory) {
        this.factory = factory;
    }

    // ------------------------------------------------------------------
    // JNamingManager implementation
    // ------------------------------------------------------------------

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

    /**
     * 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 = threadContext.get();
        if (ctx != null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                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 = clBindings.get(cl.getParent());
            if (ctx != null) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    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 (logger.isLoggable(BasicLevel.DEBUG)) {
                    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 (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "return default server Context");
            }
        }
        return ctx;
    }

    /**
     * Associate this ComponentContext 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(final Context ctx) {
        Context ret = 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(final Context ctx) {
        threadContext.set(ctx);
    }


    /**
     * Associate the specified ComponentContext 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(final Context ctx, final ClassLoader cl) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            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(final Context ctx) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }
        clientCtx = ctx;
    }

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

    /**
     * Remove the ComponentContext associated with the given classloader.
     * @param cl the classloader which is bind to the context.
     */
    public void unSetComponentContext(final ClassLoader cl) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            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;
    }

    // ------------------------------------------------------------------
    // 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 && factory != null) {
            try {
                serverContext = factory.createComponentContext("server");
            } catch (NamingException e) {
                logger.log(BasicLevel.ERROR, "cannot create serverContext:" + e);
             }
        }
        return serverContext;
    }

}
