/*
 * Copyright 2000-2001,2004 The Apache Software Foundation.
 * 
 * Licensed 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.jetspeed.engine;

import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Properties;

import javax.naming.NamingException;
import javax.servlet.ServletConfig;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jetspeed.JetspeedPortalContext;
import org.apache.jetspeed.PortalContext;
import org.apache.jetspeed.PortalReservedParameters;
import org.apache.jetspeed.components.ComponentManager;
import org.apache.jetspeed.components.jndi.JNDIComponent;
import org.apache.jetspeed.exception.JetspeedException;
import org.apache.jetspeed.pipeline.Pipeline;
import org.apache.jetspeed.request.RequestContext;
import org.apache.jetspeed.request.RequestContextComponent;
import org.apache.jetspeed.util.IsolatedLog4JLogger;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.RootCategory;
import org.apache.ojb.broker.util.ClassHelper;
import org.apache.pluto.PortletContainer;
import org.apache.pluto.PortletContainerException;
import org.apache.pluto.factory.Factory;
import org.apache.pluto.services.ContainerService;
import org.apache.pluto.services.factory.FactoryManagerService;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;


/**
 * <p>
 * AbstractEngine
 * </p>
 * <p>
 *
 * </p>
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor </a>
 * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
 * @version $Id: AbstractEngine.java,v 1.15 2005/03/23 22:50:44 ate Exp $
 *
 */
public abstract class AbstractEngine implements Engine
{

    public static final String JNDI_SUPPORT_FLAG_KEY = "portal.use.internal.jndi";
    private PortalContext context;
    private ServletConfig config = null;
    private ComponentManager componentManager = null;
    protected static final Log log = LogFactory.getLog(AbstractEngine.class);
    private static final Log console = LogFactory.getLog(CONSOLE_LOGGER);
    /** stores the most recent RequestContext on a per thread basis */
    protected boolean useInternalJNDI;
    protected String defaultPipelineName;

    /**
     * Initializes the engine with a commons configuration, starting all early
     * initable services.
     * 
     * @param configuration
     *                  a commons <code>Configuration</code> set
     * @param applicationRoot
     *                  a <code>String</code> path to the application root for
     *                  resources
     * @param
     * @throws JetspeedException
     *                   when the engine fails to initilialize
     */
    public void init( Configuration configuration, String applicationRoot, ServletConfig config ) throws JetspeedException
    {
        DateFormat format = DateFormat.getInstance();
        Date startTime = new Date();
        
        
        try
        {
            this.context = new JetspeedPortalContext(this);
            this.config = config;
            context.setApplicationRoot(applicationRoot);
            context.setConfiguration(configuration);
            useInternalJNDI = configuration.getBoolean(JNDI_SUPPORT_FLAG_KEY,
                    true);
            defaultPipelineName = configuration.getString(PIPELINE_DEFAULT, "jetspeed-pipeline");
            configuration.setProperty(JetspeedEngineConstants.APPLICATION_ROOT_KEY, applicationRoot);
            
            if(System.getProperty(JNDI_SUPPORT_FLAG_KEY) ==  null)
            {
                 System.setProperty(JNDI_SUPPORT_FLAG_KEY, String
                    .valueOf(useInternalJNDI));                 
            }
            else
            {
                // System property over rides the configurtaion                
                useInternalJNDI = Boolean.getBoolean(JNDI_SUPPORT_FLAG_KEY);
                log.warn("Internal JNDI has been flagged "+useInternalJNDI+" by the "+JNDI_SUPPORT_FLAG_KEY+" system  property.  This overrides the configuration setting of "+configuration.getBoolean(JNDI_SUPPORT_FLAG_KEY,
                        true));
            }
            
            //
            // Configure Log4J
            //
            String log4jFile = configuration.getString(LOG4J_CONFIG_FILE,
                    LOG4J_CONFIG_FILE_DEFAULT);
            log4jFile = getRealPath(log4jFile);
            Properties p = new Properties();
            p.load(new FileInputStream(log4jFile));
            p.setProperty(APPLICATION_ROOT_KEY, context.getApplicationRoot());
            Hierarchy h = new Hierarchy(new RootCategory(Level.INFO));
            new PropertyConfigurator().doConfigure(p,h);
            IsolatedLog4JLogger.setHierarchy(h);
            
            log.info("Configured log4j from " + log4jFile);
            log.info("Starting Jetspeed Engine ("+getClass().getName()+") at "+format.format(startTime));
    
            // patch up OJB
            ClassLoader ploader2 = this.getClass().getClassLoader();
            //ClassLoader ploader2 = Thread.currentThread().getContextClassLoader();
            ClassHelper.setClassLoader(ploader2);
            
            //
            // bootstrap the initable services
            //
            componentManager = initComponents(configuration, config);
            log.info("Components initialization complete");
                
        }
        catch (Throwable e)
        {
            e.printStackTrace();
            log.error(e.toString());
            throw new JetspeedException("Jetspeed Initialization exception!", e);
        }
        finally
        {            
            Date endTime = new Date();
            long elapsedTime = (endTime.getTime() - startTime.getTime()) / 1000;
            log.info("Finished starting Jetspeed Engine ("+getClass().getName()+") at "+format.format(endTime) 
                         +".  Elapsed time: "+elapsedTime+" seconds.");
        }
    }

    /**
     * Get the servlet configuration if this engine is running under a servlet
     * container.
     * 
     * @return config The servlet configuration
     */
    public ServletConfig getServletConfig()
    {
        return this.config;
    }

    /**
     * Initializes the portlet container given a servlet configuration.
     * 
     * @param config
     *                  The servlet configuration.
     */
    public void initContainer( ServletConfig config ) throws PortletContainerException
    {
        try
        {
            PortletContainer container = (PortletContainer) componentManager
                    .getComponent(PortletContainer.class);

            container.init("jetspeed", config, this, new Properties());
        }
        catch (Throwable e)
        {
            console.error("Unable to initalize Engine.", e);
            log.error("Unable to initalize Engine.", e);
            if (e instanceof PortletContainerException)
            {
                throw (PortletContainerException) e;
            }
            else
            {
                throw new PortletContainerException(e);
            }
        }
    }

    public void shutdown() throws JetspeedException
    {        
    
        try
        {
            PortletContainer container = (PortletContainer) componentManager
                    .getComponent(PortletContainer.class);
            if (container != null)
            {
                container.shutdown();
            }
    
            componentManager.stop();
        }
        catch (PortletContainerException e)
        {
            throw new JetspeedException(e);
        }
        System.gc();
    }

    public void service( RequestContext context ) throws JetspeedException
    {
        // requestContextPerThread.put(Thread.currentThread(), context);


            if (useInternalJNDI)
            {
                // bind the current JNDI context to this service thread.
                JNDIComponent jndi = (JNDIComponent) componentManager
                        .getComponent(JNDIComponent.class);
                if (jndi != null)
                {
                    try
                    {
                        jndi.bindToCurrentThread();
                    }
                    catch (NamingException e)
                    {
                        throw new JetspeedException("Unable bind jndi: "+e.toString(), e);
                    }
                }
            }
            String targetPipeline = context
                    .getRequestParameter(PortalReservedParameters.PIPELINE);
            if (null == targetPipeline)
            {                
                targetPipeline = (String)context.getAttribute(PortalReservedParameters.PIPELINE);                
            }
            // tlRequestContext.set(context);
            Pipeline pipeline = getPipeline();
            if (targetPipeline != null)
            {
                Pipeline specificPipeline = getPipeline(targetPipeline);
                if (specificPipeline != null)
                {
                    pipeline = specificPipeline;
                }
            }
            pipeline.invoke(context);
   
    }

    /**
     * Returns the context associated with this engine.
     * 
     * @return an <code>EngineContext</code> associated with this engine
     */
    public PortalContext getContext()
    {
        return this.context;
    }

    /**
     * Given a application relative path, returns the real path relative to the
     * application root
     *  
     */
    public String getRealPath( String path )
    {
        String result = "";
        String base = context.getApplicationRoot();
        if (base.endsWith(java.io.File.separator))
        {
            if (path.startsWith("/"))
            {
                result = base.concat(path.substring(1));
                return result;
            }
        }
        else
        {
            if (!path.startsWith("/"))
            {
                result = base.concat("/").concat(path);
                return result;
            }
        }
        return base.concat(path);
    }
    
    /**
     * 
     * <p>
     * initComponents
     * </p>
     * Main responsibility of the subclassed implementation of this method
     * is to provide a <code>ComponentManager</code> implementation for the 
     * Engine.
     *
     * @param configuration Usually jetspeed.properties
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws NamingException
     */
    protected abstract ComponentManager initComponents( Configuration configuration, ServletConfig servletConfig )
    throws IOException, ClassNotFoundException, NamingException;

    public Pipeline getPipeline( String pipelineName )
    {
        return (Pipeline) componentManager.getComponent(pipelineName);
    }

    public Pipeline getPipeline()
    {
        return getPipeline(defaultPipelineName);
    }

    /**
     * @see org.apache.jetspeed.engine.Engine#getCurrentRequestContext()
     */
    public RequestContext getCurrentRequestContext()
    {
        RequestContextComponent contextComponent = (RequestContextComponent) getComponentManager()
            .getComponent(RequestContextComponent.class);
        return contextComponent.getRequestContext();
    }

    public ComponentManager getComponentManager()
    {
        return this.componentManager;
    }
    /**
     * <p>
     * getFactory
     * </p>
     *
     * @see org.apache.pluto.services.factory.FactoryManagerService#getFactory(java.lang.Class)
     * @param theClass
     * @return
     */
    public Factory getFactory( Class theClass )
    {        
        return (Factory) getComponentManager().getComponent(theClass);
    }
    /**
     * <p>
     * getContainerService
     * </p>
     *
     * @see org.apache.pluto.services.PortletContainerEnvironment#getContainerService(java.lang.Class)
     * @param service
     * @return
     */
    public ContainerService getContainerService( Class service )
    {
        if(service.equals(FactoryManagerService.class))
        {
            return this;
        }

        try
        {
            return (ContainerService) getComponentManager().getComponent(service);
        }
        catch (NoSuchBeanDefinitionException e)
        {
            log.warn("No ContainerService defined for "+service.getName());
            return null;
        }
    }
}
