/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the docs/licenses/apache-1.1.txt file.
 */

package org.jboss.axis;

import org.jboss.axis.encoding.TypeMappingRegistry;
import org.jboss.axis.handlers.BasicHandler;
import org.jboss.axis.handlers.soap.SOAPService;
import org.jboss.axis.session.Session;
import org.jboss.axis.session.SimpleSession;
import org.jboss.axis.utils.JavaUtils;
import org.jboss.axis.utils.Messages;
import org.jboss.axis.utils.cache.ClassCache;
import org.jboss.logging.Logger;

import javax.xml.namespace.QName;
import javax.xml.rpc.server.ServiceLifecycle;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;


/**
 * An <code>AxisEngine</code> is the base class for AxisClient and
 * AxisServer.  Handles common functionality like dealing with the
 * handler/service registries and loading properties.
 *
 * @author Glen Daniels (gdaniels@macromedia.com)
 * @author Glyn Normington (glyn@apache.org)
 */
public abstract class AxisEngine extends BasicHandler
{
   private static Logger log = Logger.getLogger(AxisEngine.class.getName());

   // Engine property names
   public static final String PROP_XML_DECL = "sendXMLDeclaration";
   public static final String PROP_DEBUG_LEVEL = "debugLevel";
   public static final String PROP_DEBUG_FILE = "debugFile";
   public static final String PROP_DOMULTIREFS = "sendMultiRefs";
   public static final String PROP_PASSWORD = "adminPassword";
   public static final String PROP_SYNC_CONFIG = "syncConfiguration";
   public static final String PROP_SEND_XSI = "sendXsiTypes";
   public static final String PROP_ATTACHMENT_DIR = "attachments.Directory";
   public static final String PROP_ATTACHMENT_IMPLEMENTATION = "attachments.implementation";
   public static final String PROP_ATTACHMENT_CLEANUP = "attachment.DirectoryCleanUp";
   public static final String PROP_DEFAULT_CONFIG_CLASS = "axis.engineConfigClass";
   public static final String PROP_SOAP_VERSION = "defaultSOAPVersion";
   public static final String PROP_SOAP_ALLOWED_VERSION = "singleSOAPVersion";
   public static final String PROP_TWOD_ARRAY_ENCODING = "enable2DArrayEncoding";
   public static final String PROP_SEND_MINIMIZED_ELEMENTS = "axis.sendMinimizedElements";
   public static final String PROP_XML_ENCODING = "axis.xmlEncoding";

   public static final String DEFAULT_ATTACHMENT_IMPL = "org.jboss.axis.attachments.AttachmentsImpl";

   public static final String ENV_ATTACHMENT_DIR = "axis.attachments.Directory";
   public static final String ENV_SERVLET_REALPATH = "servlet.realpath";
   public static final String ENV_SERVLET_CONTEXT = "servletContext";

   // Default admin. password
   private static final String DEFAULT_ADMIN_PASSWORD = "admin";


   /**
    * Our go-to guy for configuration...
    */
   protected EngineConfiguration config;

   /**
    * Has the user changed the password yet?
    */
   protected boolean _hasSafePassword = false;

   /**
    * Should we save the engine config each time we modify it?
    */
   protected boolean shouldSaveConfig = false;

   /** Java class cache */
   //protected ClassCache classCache = new ClassCache();

   /**
    * This engine's Session.  This Session supports "application scope"
    * in the Apache SOAP sense... if you have a service with "application
    * scope", have it store things in this Session.
    */
   private Session session = new SimpleSession();

   /**
    * What actor URIs hold for the entire engine?
    */
   private ArrayList actorURIs = new ArrayList();

   /**
    * Thread local storage used for locating the active message context.
    * This information is only valid for the lifetime of this request.
    */
   private static ThreadLocal currentMessageContext = new ThreadLocal();

   /**
    * Set the active message context.
    *
    * @param mc - the new active message context.
    */
   public static void setCurrentMessageContext(MessageContext mc)
   {
      currentMessageContext.set(mc);
   }

   /**
    * Get the active message context.
    *
    * @return the current active message context
    */
   public static MessageContext getCurrentMessageContext()
   {
      return (MessageContext)currentMessageContext.get();
   }

   /**
    * Construct an AxisEngine using the specified engine configuration.
    *
    * @param config the EngineConfiguration for this engine
    */
   public AxisEngine(EngineConfiguration config)
   {
      this.config = config;
      init();
   }

   /**
    * (re)initialize - What should really go in here???
    */
   public void init()
   {
      if (log.isDebugEnabled())
      {
         log.debug("Enter: AxisEngine::init");
      }

      // The SOAP/XSD stuff is in the default TypeMapping of the TypeMappingRegistry.
      //getTypeMappingRegistry().setParent(SOAPTypeMappingRegistry.getSingleton());

      try
      {
         config.configureEngine(this);
      }
      catch (Exception e)
      {
         throw new InternalException(e);
      }

      /*Set the default attachment implementation */
      setOptionDefault(PROP_ATTACHMENT_IMPLEMENTATION,
              AxisProperties.getProperty("axis." + PROP_ATTACHMENT_IMPLEMENTATION));

      setOptionDefault(PROP_ATTACHMENT_IMPLEMENTATION, DEFAULT_ATTACHMENT_IMPL);

      if (log.isDebugEnabled())
      {
         log.debug("Exit: AxisEngine::init");
      }

   }

   /**
    * cleanup routine removes application scoped objects
    * There is a small risk of this being called more than once
    * so the cleanup should be designed to resist that event
    */
   public void cleanup()
   {
      super.cleanup();

      // Let any application-scoped service objects know that we're going
      // away...
      Enumeration keys = session.getKeys();
      if (keys != null)
      {
         while (keys.hasMoreElements())
         {
            String key = (String)keys.nextElement();
            Object obj = session.get(key);
            if (obj != null && obj instanceof ServiceLifecycle)
            {
               ((ServiceLifecycle)obj).destroy();
            }
            session.remove(key);
         }
      }
   }

   /**
    * Write out our engine configuration.
    */
   public void saveConfiguration()
   {
      if (!shouldSaveConfig)
         return;

      try
      {
         config.writeEngineConfig(this);
      }
      catch (Exception e)
      {
         log.error(Messages.getMessage("saveConfigFail00"), e);
      }
   }

   public EngineConfiguration getConfig()
   {
      return config;
   }

   public boolean hasSafePassword()
   {
      return _hasSafePassword;
   }

   public void setAdminPassword(String pw)
   {
      setOption(PROP_PASSWORD, pw);
      _hasSafePassword = true;
      saveConfiguration();
   }

   public void setShouldSaveConfig(boolean shouldSaveConfig)
   {
      this.shouldSaveConfig = shouldSaveConfig;
   }

   public Handler getHandler(String name) throws AxisFault
   {
      try
      {
         return config.getHandler(new QName(null, name));
      }
      catch (ConfigurationException e)
      {
         throw AxisFault.makeFault(e);
      }
   }

   public SOAPService getService(String name) throws AxisFault
   {
      try
      {
         return config.getService(new QName(null, name));
      }
      catch (ConfigurationException e)
      {
         throw AxisFault.makeFault(e);
      }
   }

   public Handler getTransport(String name) throws AxisFault
   {
      try
      {
         return config.getTransport(new QName(null, name));
      }
      catch (ConfigurationException e)
      {
         throw AxisFault.makeFault(e);
      }
   }

   public TypeMappingRegistry getTypeMappingRegistry()
   {
      TypeMappingRegistry tmr = null;
      try
      {
         tmr = config.getTypeMappingRegistry();
      }
      catch (ConfigurationException e)
      {
         log.error(Messages.getMessage("axisConfigurationException00"), e);
      }

      return tmr;
   }

   public Handler getGlobalRequest()
           throws ConfigurationException
   {
      return config.getGlobalRequest();
   }

   public Handler getGlobalResponse()
           throws ConfigurationException
   {
      return config.getGlobalResponse();
   }

   public ArrayList getActorURIs()
   {
      return actorURIs;
   }

   public void addActorURI(String uri)
   {
      actorURIs.add(uri);
   }

   public void removeActorURI(String uri)
   {
      actorURIs.remove(uri);
   }

   /**
    * ******************************************************************
    * Client engine access
    * <p/>
    * An AxisEngine may define another specific AxisEngine to be used
    * by newly created Clients.  For instance, a server may
    * create an AxisClient and allow deployment to it.  Then
    * the server's services may access the AxisClient's deployed
    * handlers and transports.
    * ********************************************************************
    */

   public abstract AxisEngine getClientEngine();

   /*********************************************************************
    * Administration and management APIs
    *
    * These can get called by various admin adapters, such as JMX MBeans,
    * our own Admin client, web applications, etc...
    *
    *********************************************************************
    */

   /**
    * List of options which should be converted from Strings to Booleans
    * automatically. Note that these options are common to all XML
    * web services.
    */
   private static final String[] BOOLEAN_OPTIONS = new String[]{
      PROP_DOMULTIREFS, PROP_SEND_XSI, PROP_XML_DECL, PROP_SEND_MINIMIZED_ELEMENTS
   };

   /**
    * Normalise the engine's options.
    * <p/>
    * Convert boolean options from String to Boolean and default
    * any ommitted boolean options to TRUE. Default the admin.
    * password.
    */
   public static void normaliseOptions(Handler handler)
   {
      // Convert boolean options to Booleans so we don't need to use
      // string comparisons.  Default is "true".

      for (int i = 0; i < BOOLEAN_OPTIONS.length; i++)
      {
         Object val = handler.getOption(BOOLEAN_OPTIONS[i]);
         if (val != null)
         {
            if (val instanceof Boolean)
               continue;
            if (JavaUtils.isFalse(val))
            {
               handler.setOption(BOOLEAN_OPTIONS[i], Boolean.FALSE);
               continue;
            }
         }
         else
         {
            if (!(handler instanceof AxisEngine))
               continue;
         }
         // If it was null or not "false"...
         handler.setOption(BOOLEAN_OPTIONS[i], Boolean.TRUE);
      }

      // Deal with admin password's default value.
      if (handler instanceof AxisEngine)
      {
         AxisEngine engine = (AxisEngine)handler;
         if (!engine.setOptionDefault(PROP_PASSWORD,
                 DEFAULT_ADMIN_PASSWORD))
         {
            engine.setAdminPassword((String)engine.getOption(PROP_PASSWORD));
         }
      }
   }

   /**
    * (Re-)load the global options from the registry.
    */
   public void refreshGlobalOptions() throws ConfigurationException
   {
      Hashtable globalOptions = config.getGlobalOptions();
      if (globalOptions != null)
         setOptions(globalOptions);

      normaliseOptions(this);
   }

   /**
    * accessor only, for application session
    * (could call it "engine session" instead, but named with reference
    * to Apache SOAP's notion of "application scope")
    */
   public Session getApplicationSession()
   {
      return session;
   }

   public ClassCache getClassCache()
   {
      // disabled, until I know how to do this in jboss
      // TDI 09-June-2004
      return null;
   }

}