/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.naming;

import java.util.ArrayList;

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.util.Log;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JComponentContextFactoryDelegate;

/**
 * Allow to create the context (java:) used in JOnAS application server.
 * @author Guillaume Sauthier
 */
public final class SingletonComponentContextFactory implements JComponentContextFactory {

    /**
     * Sub context name.
     */
    private static final String COMP_SUBCONTEXT = "comp";

    /**
     * app sub context name.
     */
    private static final String APP_SUBCONTEXT = "app";

    /**
     * module sub context name.
     */
    private static final String MODULE_SUBCONTEXT = "module";


    /**
     * Sub context name.
     */
    private static final String GLOBAL_SUBCONTEXT = "global";

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

    /**
     * The unique factory instance.
     */
    private static JComponentContextFactory unique;

    /**
     * {@link JComponentContextFactoryDelegate} delegates.
     */
    private ArrayList<JComponentContextFactoryDelegate> delegates = new ArrayList<JComponentContextFactoryDelegate>();

    /**
     * The java:comp/ object is shared by all Context. Only subcontext like
     * java:comp/env, etc are different for each components.
     */
    private static Context sharedCompContext = null;

    /**
     * The java:global context is shared by all the contexts.
     */
    private ComponentContext globalContext = null;

    /**
     * Default private constructor.
     * @throws NamingException if unable to create a link to the global JNDI naming
     */
    private SingletonComponentContextFactory() throws NamingException {
        super();

        // Create the shared java:comp/ context
        sharedCompContext = new ComponentContext(COMP_SUBCONTEXT);

        // 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 JComponentContextFactory.
     * @return a <code>JComponentContextFactory</code> the unique instance.
     * @throws NamingException if it failed.
     */
    public static synchronized JComponentContextFactory getInstance() throws NamingException {
        if (unique == null) {
            unique = new SingletonComponentContextFactory();
        }
        return unique;
    }

    /**
     * Add the given {@link JComponentContextFactoryDelegate} to this
     * NamingManager instance.
     * @param extension Added delegate
     * @throws NamingException if delegate is not added
     */
    public synchronized void addDelegate(final JComponentContextFactoryDelegate extension) throws NamingException {
        logger.log(BasicLevel.DEBUG, "add :" + extension);
        delegates.add(extension);
        extension.modify(sharedCompContext);
    }

    /**
     * Remove the given {@link JComponentContextFactoryDelegate} from this
     * NamingManager instance.
     * @param extension Removed delegate
     * @throws NamingException if delegate is not removed
     */
    public synchronized void removeDelegate(final JComponentContextFactoryDelegate extension) throws NamingException {
        logger.log(BasicLevel.DEBUG, "extension:" + extension);
        delegates.remove(extension);
        extension.undo(sharedCompContext);
    }


    /**
     * Create {@link Context} for component environments.
     * The returned context is a Java EE Component Context.
     * @param id the Context ID.
     * @return Naming {@link Context} for component environment
     * @throws NamingException If exception encountered when creating namespace.
     */
    public synchronized Context createComponentContext(final String id) throws NamingException {
        return this.createComponentContext(id, null, null);
    }

    /**
     * Create {@link Context} for component environments. The returned context
     * is a Java EE Component Context.
     * @param id the Context ID.
     * @param moduleContext the application context shared by all the components
     *        in a module
     * @param appContext the application context shared by all the applications
     * @return Naming {@link Context} for component environment
     * @throws NamingException If exception encountered when creating namespace.
     */
    public synchronized Context createComponentContext(final String id, final Context moduleContext, final Context appContext)
            throws NamingException {

        logger.log(BasicLevel.DEBUG, id);

        // Create a new environment
        ComponentContext ctx = new ComponentContext(id);

        // Create subContext
        ComponentContext compCtx = (ComponentContext) ctx.createSubcontext(COMP_SUBCONTEXT);

        // Add global
        ctx.addBinding(GLOBAL_SUBCONTEXT, getGlobalContext());

        // Add the wrapped shared java:comp context
        compCtx.addWrapped(sharedCompContext);

        // module may use comp context
        Context moduleCtx = null;
        if (moduleContext == null) {
            moduleCtx = compCtx;
        } else {
            moduleCtx = moduleContext;
        }
        ctx.addBinding(MODULE_SUBCONTEXT, moduleCtx);


        // App context (if not defined, reuse module context)
        Context appCtx = null;
        if (appContext == null) {
            appCtx = moduleCtx;
        } else {
            appCtx = appContext;
        }

        ctx.addBinding(APP_SUBCONTEXT, appCtx);

        return ctx;

    }



    protected synchronized Context getGlobalContext() {
        if (globalContext != null) {
            return globalContext;
        }
        
        globalContext = new ComponentContext(GLOBAL_SUBCONTEXT);
        // 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(SingletonComponentContextFactory.class.getClassLoader(), ie);
        if (result.hasException()) {
            throw new IllegalStateException("Unable to get InitialContext", result.getException());
        }
        Context jndiContext = result.getResult();
        globalContext.addWrapped(jndiContext, true);
        return globalContext;
        
    }


}
