/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@objectweb.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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.objectweb.jonas.web.wrapper.catalina55;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.naming.Context;

import org.objectweb.jonas.web.wrapper.CatalinaJWebContainerService;
import org.objectweb.jonas.server.LoaderManager;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.web.AbsJWebContainerServiceImplMBean;
import org.objectweb.jonas.web.JWebContainerService;
import org.objectweb.jonas.web.JWebContainerServiceException;
import org.objectweb.jonas.web.War;


/**
 * Wrap the Catalina 5.0 Web Container Service because Catalina use Digester to parse its server.xml
 *
 * @author Guillaume Sauthier
 */
public class CatalinaJWebContainerServiceWrapper implements CatalinaJWebContainerService, AbsJWebContainerServiceImplMBean {

    /**
     * Catalina ClassLoader
     */
    private ClassLoader catalinaLoader = null;

    /**
     * CatalinaJWebContainerService instance
     */
    private Object catalinaService = null;

    /**
     * cached Method Map
     */
    private Map methods = null;

    /**
     * Catalina service classname
     */
    private static final String CATALINA_SERVICE_CLASSNAME = "org.objectweb.jonas.web.catalina55.CatalinaJWebContainerServiceImpl";

    /**
     * Construct the instance and init the service.
     * @throws ServiceException when Catalina Loader cannot be found
     */
    public CatalinaJWebContainerServiceWrapper() throws ServiceException {
        LoaderManager lm = LoaderManager.getInstance();
        try {
            catalinaLoader = lm.getCatalinaLoader();
        } catch (Exception e) {
            // TODO add i18n error message
            throw new ServiceException("Cannot get Catalina ClassLoader", e);
        }
        methods = new Hashtable();

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }

    }

    /**
     * Deploy the given wars of an ear file with the specified parent
     * classloader (ejb classloader or ear classloader). (This method
     * is only used for the ear applications, not for the
     * web applications).
     * @param ctx the context containing the configuration
     * to deploy the wars.<BR>
     * This context contains the following parameters :<BR>
     *    - urls the list of the urls of the wars to deploy.<BR>
     *    - earURL the URL of the ear application file.<BR>
     *    - parentClassLoader the parent classLoader of the wars.<BR>
     *    - earClassLoader the ear classLoader of the j2ee app.<BR>
     *    - altDDs the optional URI of deployment descriptor.<BR>
     *    - contextRoots the optional context root of the wars.<BR>
     * @throws JWebContainerServiceException if an error occurs during
     * the deployment.
     */
    public void deployWars(Context ctx) throws JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }

        // method invokation
        Method m = (Method) methods.get("deployWars");
        if (m == null) {
            m = getMethod("deployWars", new Class[] {Context.class});
        }

        invoke(m, new Object[] {ctx});
    }

    /**
     * Get the war identified by its URL (.war).
     * @param url the URL of the war to get.
     * @return the war indentified by its URL, or null if the war is not found.
     */
    public War getWar(URL url) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }

        // method invokation
        Method m = (Method) methods.get("getWar");
        if (m == null) {
            m = getMethod("getWar", new Class[] {URL.class});
        }

        return (War) invoke(m, new Object[] {url});

    }
    /**
     * Invoke a method with the given args
     * @param m method to invoke
     * @param params arguments
     * @return the object if any
     */
    private Object invoke(Method m, Object[] params) {
        ClassLoader old = null;
        try {
            old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(catalinaLoader);
            return m.invoke(catalinaService, params);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof Error) {
                throw (Error) e.getTargetException();
            } else if (e.getTargetException() instanceof ServiceException) {
                throw (ServiceException) e.getTargetException();
            } else {
//              TODO add i18n error message
                throw new ServiceException("Problems when invoking " + m.getName(), e.getTargetException());
            }
        } catch (Exception e) {
//          TODO add i18n error message
            throw new ServiceException("Problems when invoking " + m.getName(), e);
        } finally {
            if (old != null) {
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    }

    /**
     * Gets the method object for a given method name /types
     * @param methodName name of the method
     * @param paramTypes arguments
     * @throws ServiceException if the method cannot be retrieved
     * @return the wanted method
     */
    private Method getMethod(String methodName, Class[] paramTypes) throws ServiceException {
        try {
            Method m = catalinaService.getClass().getMethod(methodName, paramTypes);
            methods.put(methodName, m);
            return m;
        } catch (Exception e) {
            throw new ServiceException("Problems when retrieving " + methodName + " method", e);
        }
    }

    /**
     * @return Returns a new CatalinaService instance
     * @throws ServiceException if the instance cannot be retrieved
     */
    private Object getCatalinaServiceInstance() throws ServiceException {
        try {
            Class service = catalinaLoader.loadClass(CATALINA_SERVICE_CLASSNAME);
            return service.newInstance();
        } catch (Exception e) {
            throw new JWebContainerServiceException("Problems when loading " + CATALINA_SERVICE_CLASSNAME, e);
        }
    }

    /**
     * Undeploy the given wars of an ear file with the specified parent
     * classloader (ejb classloader or ear classloader). (This method
     * is only used for the ear applications, not for the
     * war applications).
     * @param urls the list of the urls of the wars to undeploy.
     */
    public void unDeployWars(URL[] urls) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("unDeployWars");
        if (m == null) {
            m = getMethod("unDeployWars", new Class[] {URL[].class});
        }

        invoke(m, new Object[] {urls});

    }

    /**
     * Make a cleanup of the cache of deployment descriptor. This method must
     * be invoked after the ear deployment by the EAR service.
     * @param earClassLoader the ClassLoader of the ear application to
     * remove from the cache.
     */
    public void removeCache(ClassLoader earClassLoader) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("removeCache");
        if (m == null) {
            m = getMethod("removeCache", new Class[] {ClassLoader.class});
        }

        invoke(m, new Object[] {earClassLoader});

    }

    /**
     * Register a WAR by delegating the operation to the registerWar()
     * method. This is used for JMX management.
     * @param fileName the name of the war to deploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the registration failed.
     */
    public void registerWarMBean(String fileName) throws RemoteException, JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("registerWarMBean");
        if (m == null) {
            m = getMethod("registerWarMBean", new Class[] {String.class});
        }

        invoke(m, new Object[] {fileName});

    }

    /**
     * Unregister a WAR by delegating the operation to the unRegisterWar()
     * method. This is used for JMX management.
     * @param fileName the name of the war to undeploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the unregistration failed.
     */
    public void unRegisterWarMBean(String fileName) throws RemoteException, JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("unRegisterWarMBean");
        if (m == null) {
            m = getMethod("unRegisterWarMBean", new Class[] {String.class});
        }

        invoke(m, new Object[] {fileName});

    }

    /**
     * Return the list of installed web applications.
     * The WAR files or the directories with expanded web application are searched
     * in JONAS_BASE/webapps and all webapps directories 'autoload'.
     * @return The list of WAR files or the directories with expanded web application found
     * @throws Exception if the list can't be retrieved
     */
    public List getInstalledWars() throws Exception {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getInstalledWars");
        if (m == null) {
            m = getMethod("getInstalledWars", new Class[] {});
        }

        return (List) invoke(m, new Object[] {});

    }

    /**
     * @return current number of wars deployed in the JOnAS server
     */
    public Integer getCurrentNumberOfWars() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getCurrentNumberOfWars");
        if (m == null) {
            m = getMethod("getCurrentNumberOfWars", new Class[] {});
        }

        return (Integer) invoke(m, new Object[] {});

    }

    /**
     * This method is added temporarily. It will disapear when Wars will have their associated MBeans
     * (when Wars will become manageable)
     * @return the names of the wars currently deployed in the JOnAS server
     */
    public Set getWarNames() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getWarNames");
        if (m == null) {
            m = getMethod("getWarNames", new Class[] {});
        }

        return (Set) invoke(m, new Object[] {});

    }

    /**
     * Test if the specified filename is already deployed or not
     * @param fileName the name of the war file.
     * @return true if the war is deployed, else false.
     */
    public boolean isWarLoaded(String fileName) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("isWarLoaded");
        if (m == null) {
            m = getMethod("isWarLoaded", new Class[] {String.class});
        }

        return ((Boolean) invoke(m, new Object[] {fileName})).booleanValue();

    }
    
    /**
     * Test if the specified unpack name is already deployed or not. This method
     * is defined in the {@link JWebContainerService} interface.
     * @param unpackName the name of the war file.
     * @return true if the war is deployed, else false.
     */
    public boolean isWarDeployedByWorkName(final String unpackName) {
        
        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("isWarDeployedByWorkName");
        if (m == null) {
            m = getMethod("isWarDeployedByWorkName", new Class[] {String.class});
        }

        return ((Boolean) invoke(m, new Object[] {unpackName})).booleanValue();
    }

    /**
     * Gets the name of the server which is the web container
     * @return the name of the server which is the web container
     */
    public String getServerName() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getServerName");
        if (m == null) {
            m = getMethod("getServerName", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Gets the version of the server which is the web container
     * @return the version of the server which is the web container
     */
    public String getServerVersion() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getServerVersion");
        if (m == null) {
            m = getMethod("getServerVersion", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Return the list of all loaded web applications.
     *
     * @return The list of deployed web applications
     */
    public List getDeployedWars() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getDeployedWars");
        if (m == null) {
            m = getMethod("getDeployedWars", new Class[] {});
        }

        return (List) invoke(m, new Object[] {});

    }

    /**
     * Return the list of installed web applications ready to deploy.
     *
     * @return The list of deployable web applications
     * @throws Exception if the list can't be retrieved
     */
    public List getDeployableWars() throws Exception {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getDeployableWars");
        if (m == null) {
            m = getMethod("getDeployableWars", new Class[] {});
        }

        return (List) invoke(m, new Object[] {});

    }

    /**
     * Return the list of "autoload" directories for web applications.
     * @return The list of all "autoload" directories
     */
    public List getAutoloadDirectories() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getAutoloadDirectories");
        if (m == null) {
            m = getMethod("getAutoloadDirectories", new Class[] {});
        }

        return (List) invoke(m, new Object[] {});

    }

    /**
     * Return the WebApps directory.
     * @return The WebApps directory
     */
    public String getWebappsDirectory() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getWebappsDirectory");
        if (m == null) {
            m = getMethod("getWebappsDirectory", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Initialize the Catalina service.
     * @param ctx the configuration context of the service.
     * @throws ServiceException if the initialization failed.
     * @see org.objectweb.jonas.service.Service#init(javax.naming.Context)
     */
    public void init(Context ctx) throws ServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("init");
        if (m == null) {
            m = getMethod("init", new Class[] {Context.class});
        }

        invoke(m, new Object[] {ctx});

    }

    /**
     * Start the Catalina service in a new thread
     * @throws ServiceException if the startup failed.
     * @see org.objectweb.jonas.service.Service#start()
     */
    public void start() throws ServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("start");
        if (m == null) {
            m = getMethod("start", new Class[] {});
        }

        invoke(m, new Object[] {});
    }

    /**
     * Stop the Catalina service.
     * @throws ServiceException if the stop failed.
     * @see org.objectweb.jonas.service.Service#stop()
     */
    public void stop() throws ServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("stop");
        if (m == null) {
            m = getMethod("stop", new Class[] {});
        }

        invoke(m, new Object[] {});

    }

    /**
     * @return true if the service is started, false otherwise
     */
    public boolean isStarted() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("isStarted");
        if (m == null) {
            m = getMethod("isStarted", new Class[] {});
        }

        return ((Boolean) invoke(m, new Object[] {})).booleanValue();

    }

    /**
     * Set the service's name
     * @param name to set
     */
    public void setName(String name) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("setName");
        if (m == null) {
            m = getMethod("setName", new Class[] {String.class});
        }

        invoke(m, new Object[] {name});

    }

    /**
     * @return the service's name
     */
    public String getName() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getName");
        if (m == null) {
            m = getMethod("getName", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Return the Default host name of the web container.
     * @return the Default host name of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Host.
     */
    public String getDefaultHost() throws JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getDefaultHost");
        if (m == null) {
            m = getMethod("getDefaultHost", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Return the Default HTTP port number of the web container (can
     * be null if multiple HTTP connector has been set).
     * @return the Default HTTP port number of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Http port.
     */
    public String getDefaultHttpPort() throws JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getDefaultHttpPort");
        if (m == null) {
            m = getMethod("getDefaultHttpPort", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Return the Default HTTPS port number of the web container (can
     * be null if multiple HTTPS connector has been set).
     * @return the Default HTTPS port number of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Https port.
     */
    public String getDefaultHttpsPort() throws JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getDefaultHttpsPort");
        if (m == null) {
            m = getMethod("getDefaultHttpsPort", new Class[] {});
        }

        return (String) invoke(m, new Object[] {});

    }

    /**
     * Return the class loader of the given warURL. Unpack the associated war
     * and build the loader if it's not in the cache.
     * @param warURL the url of the war we want to get the loader
     * @param earAppName the name of the ear application containing
     * the war. May be null in non ear case.
     * @param parentLoader the ejb class loader of the ear.
     * May be null in non ear case.
     * @return the class loader of the given warURL.
     * @throws JWebContainerServiceException if the process failed.
     */
    public URLClassLoader getClassLoader(URL warURL, String earAppName, ClassLoader parentLoader) throws JWebContainerServiceException {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getClassLoader");
        if (m == null) {
            m = getMethod("getClassLoader", new Class[] {URL.class, String.class, ClassLoader.class});
        }

        return (URLClassLoader) invoke(m, new Object[] {warURL, earAppName, parentLoader});

    }

    /**
     * The server is started ?
     * @return boolean true if the catalina container is running.
     */
    public boolean isTomcatStarted() {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("isTomcatStarted");
        if (m == null) {
            m = getMethod("isTomcatStarted", new Class[] {});
        }

        return ((Boolean) invoke(m, new Object[] {})).booleanValue();

    }

    /**
     * @param url the URL of the webapp
     * @return Returns the ClassLoader used to link a JNDI environnment to a webapp
     */
    public ClassLoader getContextLinkedClassLoader(URL url) {

        if (catalinaService == null) {
            catalinaService = getCatalinaServiceInstance();
        }
        // method invokation
        Method m = (Method) methods.get("getContextLinkedClassLoader");
        if (m == null) {
            m = getMethod("getContextLinkedClassLoader", new Class[] {URL.class});
        }

        return (ClassLoader) invoke(m, new Object[] {url});

    }

}
