/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */


package org.apache.axis.transport.http;

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;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 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()
   {
      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(getServletContext(), 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(context);
         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);
            storeEngine(context, 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(ServletContext context, AxisServer engine)
   {
      if (engine == null)
      {
         context.removeAttribute(ATTR_AXIS_ENGINE);
      }
      else
      {
         context.setAttribute(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 context
    * @return the engine or null if either the engine couldnt be found or
    *         the attribute wasnt of the right type
    */
   private static AxisServer retrieveEngine(ServletContext context)
   {
      Object contextObject = context.getAttribute(ATTR_AXIS_ENGINE);
      if (contextObject instanceof AxisServer)
      {
         return (AxisServer)contextObject;
      }
      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(context)
              .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);

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

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

}
