/*
 * Copyright 2002-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.axis.transport.http;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.axis.AxisEngine;
import org.apache.axis.AxisFault;
import org.apache.axis.AxisProperties;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.configuration.EngineConfigurationFactoryFinder;
import org.apache.axis.server.AxisServer;
import org.apache.axis.utils.JavaUtils;
import org.apache.commons.logging.Log;

/**
 * Base class for servlets used in axis, has common methods
 * to get and save the engine to a common location, currently the
 * webapp's context, though some alternate persistence mechanism is always
 * possible. Also has a load counter shared by all servlets; tracks the
 * # of active http requests open to any of the subclasses.
 * @author Steve Loughran
 */

public class AxisServletBase extends HttpServlet {

    /**
     * per-instance cache of the axis server
     */
    protected AxisServer axisServer = null;

    private static Log log =
        LogFactory.getLog(AxisServlet.class.getName());
        
    private static boolean isDebug = false;

    /**
     *  count number of service requests in progress
     */
    private static int loadCounter = 0;

    /**
     *  and a lock
     */
    private static Object loadCounterLock = new Object();

    /**
     * name of the axis engine to use in the servlet context
     */
    protected static final String ATTR_AXIS_ENGINE =
        "AxisEngine" ;

    /**
     *  Cached path to our WEB-INF directory
     */
    private String webInfPath = null;

    /**
     * Cached path to our "root" dir
     */
    private String homeDir = null;

    /**
     * flag set to true for a 'production' server
     */
    private boolean isDevelopment;

    /**
     * property name for a production server
     */
    private static final String INIT_PROPERTY_DEVELOPMENT_SYSTEM=
               "axis.development.system";


    /**
     * our initialize routine; subclasses should call this if they override it
     */
    public void init() throws javax.servlet.ServletException {
        ServletContext context = getServletConfig().getServletContext();

        webInfPath = context.getRealPath("/WEB-INF");
        homeDir = context.getRealPath("/");

        isDebug = log.isDebugEnabled();
        if(log.isDebugEnabled()) log.debug("In AxisServletBase init");
        isDevelopment= JavaUtils.isTrueExplicitly(getOption(context,
                        INIT_PROPERTY_DEVELOPMENT_SYSTEM, null));

    }

    /**
     * Destroy method is called when the servlet is going away.  Pass this
     * down to the AxisEngine to let it clean up...  But don't create the
     * engine if it hasn't already been created.
     * @todo Fixme for multiple servlets.
     * This has always been slightly broken
     * (the context's copy stayed around), but now we have extracted it into
     * a superclass it is blatantly broken.
     */
    public void destroy() {
        super.destroy();

        //if we have had anything to do with creating an axis server
        if (axisServer != null) {
            //then we lock it
            synchronized(axisServer) {
                if (axisServer != null) {
                    //clean it up
                    axisServer.cleanup();
                    //and erase our history of it
                    axisServer =null;
                    storeEngine(this,null);
                }
            }
        }
    }

    /**
     * get the engine for this servlet from cache or context
     * @return
     * @throws AxisFault
     */
    public AxisServer getEngine() throws AxisFault {
        if (axisServer == null)
            axisServer = getEngine(this);
        return axisServer;
    }


    /**
     * This is a uniform method of initializing AxisServer in a servlet
     * context.
     * @todo add catch for not being able to cast the context attr to an
     * engine and reinit the engine if so.
     */
    public static AxisServer getEngine(HttpServlet servlet) throws AxisFault
    {
        AxisServer engine = null;
        if (isDebug)
            log.debug("Enter: getEngine()");

        ServletContext context = servlet.getServletContext();
        synchronized (context) {
            engine = retrieveEngine(servlet);
            if (engine == null) {
                Map environment = getEngineEnvironment(servlet);

                // Obtain an AxisServer by using whatever AxisServerFactory is
                // registered.  The default one will just use the provider we
                // passed in, and presumably JNDI ones will use the ServletContext
                // to figure out a JNDI name to look up.
                //
                // The point of doing this rather than just creating the server
                // manually with the provider above is that we will then support
                // configurations where the server instance is managed by the
                // container, and pre-registered in JNDI at deployment time.  It
                // also means we put the standard configuration pattern in one
                // place.
                engine = AxisServer.getServer(environment);
//              attach the AxisServer with the current Servlet
                engine.setName(servlet.getServletName());
                storeEngine(servlet, engine);
            }
        }

        if (isDebug)
            log.debug("Exit: getEngine()");

        return engine;
    }

    /**
     * put the engine back in to the context.
     * @param context servlet context to use
     * @param engine reference to the engine. If null, the engine is removed
     */
    private static void storeEngine(HttpServlet servlet, AxisServer engine) {
        ServletContext context = servlet.getServletContext();
        String axisServletName = servlet.getServletName();
        if (engine == null) {
            context.removeAttribute(axisServletName + ATTR_AXIS_ENGINE);
            // find if there is other AxisEngine in Context
            AxisServer server = (AxisServer) context.getAttribute(ATTR_AXIS_ENGINE);

            // no other AxisEngine in ServletContext
            if (server != null && servlet.getServletName().equals(server.getName())) {
                context.removeAttribute(ATTR_AXIS_ENGINE);
            }
        } else {
            if (context.getAttribute(ATTR_AXIS_ENGINE) == null) {
                // first Axis servlet to store its AxisEngine
                // use default name
                context.setAttribute(ATTR_AXIS_ENGINE, engine);
            }
            context.setAttribute(axisServletName + ATTR_AXIS_ENGINE, engine);
        }
    }

    /**
     * Get an engine from the servlet context; robust againt serialization
     * issues of hot-updated webapps. Remember than if a webapp is marked
     * as distributed, there is more than 1 servlet context, hence more than
     * one AxisEngine instance
     * @param servlet
     * @return the engine or null if either the engine couldnt be found or
     *         the attribute wasnt of the right type
     */
    private static AxisServer retrieveEngine(HttpServlet servlet) {
        Object contextObject = servlet.getServletContext().getAttribute(servlet.getServletName() + ATTR_AXIS_ENGINE);
        if (contextObject == null) {
            // if AxisServer not found :
            // fall back to the "default" AxisEngine
            contextObject = servlet.getServletContext().getAttribute(ATTR_AXIS_ENGINE);
        }
        if (contextObject instanceof AxisServer) {
            AxisServer server = (AxisServer) contextObject;
            // if this is "our" Engine
            if (server != null && servlet.getServletName().equals(server.getName())) {
                return server;
            }
            return null;
        } else {
            return null;
        }
     }

    /**
     * extract information from the servlet configuration files
     * @param servlet
     * @return
     */
    protected static Map getEngineEnvironment(HttpServlet servlet) {
        Map environment = new HashMap();

        String attdir= servlet.getInitParameter(AxisEngine.ENV_ATTACHMENT_DIR);
        if (attdir != null)
            environment.put(AxisEngine.ENV_ATTACHMENT_DIR, attdir);

        ServletContext context = servlet.getServletContext();
        environment.put(AxisEngine.ENV_SERVLET_CONTEXT, context);

        String webInfPath = context.getRealPath("/WEB-INF");
        if (webInfPath != null)
            environment.put(AxisEngine.ENV_SERVLET_REALPATH,
                            webInfPath + File.separator + "attachments");

        EngineConfiguration config =
            EngineConfigurationFactoryFinder.newFactory(servlet)
                    .getServerEngineConfig();

        if (config != null) {
            environment.put(EngineConfiguration.PROPERTY_NAME, config);
        }

        return environment;
    }


    /**
     *  get a count of the # of services running. This is only
     *  ever an approximate number in a busy system
     *
     * @return    The TotalServiceCount value
     */

    public static int getLoadCounter() {
            return loadCounter;
    }

    /**
     * thread safe lock counter increment
     */
    protected static void incLockCounter() {
        synchronized(loadCounterLock) {
            loadCounter++;
        }
    }

    /**
     * thread safe lock counter decrement
     */
    protected static void decLockCounter() {
        synchronized(loadCounterLock) {
            loadCounter--;
        }
    }

    /**
     * subclass of service method that tracks entry count; calls the
     * parent's implementation to have the http method cracked and delegated
     * to the doGet, doPost method.
     * @param req request
     * @param resp response
     * @throws ServletException something went wrong
     * @throws IOException something different went wrong
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        incLockCounter();
        try {
            super.service(req, resp);
        }
        finally {
            decLockCounter();
        }
    }
    
    /**
     * extract the base of our webapp from an inbound request
     *
     * @param request request containing http://foobar/axis/services/something
     * @return some URL like http://foobar:8080/axis/
     */
    protected String getWebappBase(HttpServletRequest request) {
        StringBuffer baseURL=new StringBuffer(128);
        baseURL.append(request.getScheme());
        baseURL.append("://");
        baseURL.append(request.getServerName());
        if(request.getServerPort()!=80) {
            baseURL.append(":");
            baseURL.append(request.getServerPort());
        }
        baseURL.append(request.getContextPath());
        return baseURL.toString();
    }

    /**
     * what is the servlet context
     * @return get the context from the servlet config
     */
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }

    /**
     * accessor to webinf
     * @return path to WEB-INF/ in the local filesystem
     */
    protected String getWebInfPath() {
        return webInfPath;
    }

    /**
     * what is the root dir of the applet?
     * @return path of root dir
     */
    protected String getHomeDir() {
        return homeDir;
    }

    /**
     * Retrieve option, in order of precedence:
     * (Managed) System property (see discovery.ManagedProperty),
     * servlet init param, context init param.
     * Use of system properties is discouraged in production environments,
     * as it overrides everything else.
     */
    protected String getOption(ServletContext context,
                             String param,
                             String dephault)
    {
        String value = AxisProperties.getProperty(param);

        if (value == null)
            value = getInitParameter(param);

        if (value == null)
            value = context.getInitParameter(param);
        try {
            AxisServer engine = getEngine(this);
            if (value == null && engine != null)
                value = (String) engine.getOption(param);
        } catch (AxisFault axisFault) {
        }

        return (value != null) ? value : dephault;
    }

    /**
     * probe for the system being 'production'
     * @return true for a dev system.
     */
    public boolean isDevelopment() {
        return isDevelopment;
    }

}
