/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 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.resource;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.resource.spi.XATerminator;
import javax.resource.spi.work.WorkManager;

import org.apache.commons.modeler.ManagedBean;
import org.objectweb.jonas.common.JModule;
import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.container.EJBService;
import org.objectweb.jonas.jmx.J2eeObjectName;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.jtm.TransactionService;
import org.objectweb.jonas.management.JonasMBeanTools;
import org.objectweb.jonas.naming.CompNamingContext;
import org.objectweb.jonas.server.LoaderManager;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.jonas_lib.JWorkManager;
import org.objectweb.jonas_rar.deployment.lib.wrapper.RarManagerWrapper;
import org.objectweb.transaction.jta.TransactionManager;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * JCA resource service implmentation
 * @author Philippe Coq
 * Contributor(s):
 * JOnAS 2.4 Sebastien Chassande-Barrioz (sebastien.chassande@inrialpes.fr)
 * JOnAS 3.0 Eric Hardesty (Eric.Hardesty@bull.com)
 * JOnAS 4.0 Adriana Danes (JSR 77 + use of Jakarta Modeler Component : http://jakarta.apache.org/commons/modeler)
 *           Eric Hardesty (J2CA 1.5)
 *
 */
public class ResourceServiceImpl extends AbsServiceImpl implements
    ResourceService,
    ResourceServiceImplMBean {

    // Loggers used in the ResourceService
    /**
     * Main logger
     */
    private static Logger logger = null;
    /**
     * Pool infomation logger
     */
    private static Logger poolLogger = null;
    /**
     * Config property setter logger
     */
    private static Logger setterLogger = null;
    /**
     * Management logger
     */
    private static Logger manageLogger = null;

    // Properties for inits

    // JCA resource service configuration parameters
    /**
     * Autoload directory property name
     */
    public static final String AUTOLOADDIR = "jonas.service.resource.autoloaddir";
    /**
     * Service class property name
     */
    public static final String CLASS = "jonas.service.resource.class";
    /**
     * Jndiname property name
     */
    public static final String JNDI_NAME = "jndiname";
    /**
     * Rar object property name
     */
    public static final String RAR_OBJNAME = "rarobjname";
    /**
     * Factory offset property name
     */
    public static final String FACTORY_OFFSET = "factoryoffset";
    /**
     * Factory type property name
     */
    public static final String FACTORY_TYPE = "factorytype";
    /**
     * Rar filename property name
     */
    public static final String RAR_FILENAME = "rarfilename";
    /**
     * Jndiname link property name
     */
    public static final String LNK_JNDI_NAME = "lnkjndiname";
    /**
     * Link Rar filename property name
     */
    public static final String LNK_RAR_FILENAME = "lnkrarfilename";
    /**
     * Jonas ra.xml property name
     */
    public static final String JONAS_RA_XML = "jonasraxml";
    /**
     * ra.xml property name
     */
    public static final String RA_XML = "raxml";
    /**
     * Parsing validation property name
     */
    public static final String PARSINGWITHVALIDATION = "jonas.service.resource.parsingwithvalidation";
    /**
     * Resources list property name
     */
    public static final String RESOURCE_LIST = "jonas.service.resource.resources";
    /**
     * Thread timeout
     */
    public static final String THREADWAITTIMEOUT = "jonas.service.resource.threadwaittimeout";
    /**
     * Minimum number of work threads property name
     */
    public static final String MINWORKTHREADS = "jonas.service.resource.minworkthreads";
    /**
     * Maximum number of work threads property name
     */
    public static final String MAXWORKTHREADS = "jonas.service.resource.maxworkthreads";
    /**
     * Work max execution timeout property name
     */
    public static final String EXECTIMEOUT = "jonas.service.resource.worktimeout";

    /**
     * Default work thread timeout
     */
    public static final int DEF_WRK_THREADWAITTIMEOUT = 60;
    /**
     * Default number of work threads
     */
    public static final int DEF_WRK_THREADS = 5;
    /**
     * Default max number of work threads
     */
    public static final int DEF_MAX_WRK_THREADS = 80;
    /**
     * Maximum work execution timeout (0 is unlimited)
     */
    public static final int DEF_EXEC_TIME = 0;

    /**
     * Hashtable mapping a jndiname to an RAR object
     */
//    private static Hashtable jndiName2RA = new Hashtable();
    /**
     * Hashtable mapping a filename to an RAR object
     */
//    private static Hashtable fileName2RA = new Hashtable();
    /**
     * Hashtable mapping a jndiname to an "external" factory
     */
//    private static Hashtable jndiName2Factory = new Hashtable();

    /**
     * This vector is list of RAR files that will be deployed after all
     * other RAR files have been deployed.  The problem is that these RAR
     * files are associated with another RAR file that has not been processed.
     */
    private Vector delayedRAs = new Vector();
    /**
     * boolean to determine if processing of delayed Rars has begun
     */
    private boolean processingDelayed = false;

    /**
     * The transaction service for this server
     */
    private TransactionService ts = null;
    /**
     * The transaction manager for this server
     */
    private TransactionManager tm = null;

    /**
     * Reference to a MBean server.
     */
    private MBeanServer mbeanServer = null;

    /**
     * Autoload directory names
     */
    private Vector autoNames = null;
    /**
     * List of resource names
     */
    private Vector resourceNames = null;


    // J2EE CA 1.5 objects
    /**
     * Work Manager for the Resource service
     */
    private WorkManager workMgr = null;
    /**
     * BootstrapContext for the Resource service
     */
    private ResourceBootstrapContext bootCtx = null;

    /**
     *  The name of the JONAS_BASE directory
     */
    public static final String JONAS_BASE = JProp.getJonasBase();

    /**
     * The name of the working apps directory.
     */
    public static final String WORK_RARS_DIR = JProp.getWorkDir() + File.separator + "rars";

    /**
     *  The name of the rars directory
     */
    public static final String RARSDIR = JProp.getJonasBase() + File.separator + "rars";

    /**
     * Application parent classloader
     */
    private ClassLoader appsClassLoader;

    /**
     * Default construtor for ResourceService
     */
    public ResourceServiceImpl() {
    }

    //IMPLEMENTATION OF 'AbsServiceImpl' ABSTRACT CLASS
    /**
     * - Get the loggers
     * - Get the global jndi context
     * - Get the list of the resource adapters. The list is reachable in the
     * -  context parameter under the name RESOURCE_LIST.
     * - Get the transaction manager into the jndi
     * - Set the XML validation property
     * @param ctx Context
     */
    public void doInit(Context ctx) {
        if (logger == null) {
            logger = Log.getLogger(Log.JONAS_JCA_PREFIX + ".process");
        }
        if (poolLogger == null) {
            poolLogger = Log.getLogger(Log.JONAS_JCA_PREFIX + ".pool");
        }
        if (setterLogger == null) {
            setterLogger = Log.getLogger(Log.JONAS_JCA_PREFIX + ".setters");
        }
        if (manageLogger == null) {
            manageLogger = Log.getLogger(Log.JONAS_JCA_PREFIX + ".management");
        }

        try {
            LoaderManager lm = LoaderManager.getInstance();
            appsClassLoader = lm.getAppsLoader();
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get the Applications ClassLoader from RAR Container Service: " + e);
            throw new ServiceException("Cannot get the Applications ClassLoader from RAR Container Service", e);
        }

        resourceNames = new Vector();
        autoNames = new Vector();

        // Add the rars of the jonas.service.resource.autoloaddir property
        String dirValue = null;
        try {
            dirValue = (String) ctx.lookup(AUTOLOADDIR);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "autoloaddir= " + dirValue);
            }
        } catch (NamingException e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No autoloaddir value specified in context, usually for client container");
            }
        }
        if (dirValue != null) {
            StringTokenizer st = new StringTokenizer(dirValue, ",");
            String dirName = null;
            while (st.hasMoreTokens()) {
                dirName = normalizePath(st.nextToken().trim());
                addRars(dirName);
            }
        }

        // Get the list of the resource adapter names
        try {
            String rs = (String) ctx.lookup(RESOURCE_LIST);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "resource list= " + rs);
            }
            if (rs != null) {
                StringTokenizer st = new StringTokenizer(rs, ",");
                String resFilename = null;
                while (st.hasMoreTokens()) {
                    resFilename = normalizePath(st.nextToken().trim());
                    resourceNames.add(resFilename);
                }
            }
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot lookup the configuration context at Resource service starting");
        }

        // Get a reference to the Transaction service
        try {
            ServiceManager sm = ServiceManager.getInstance();
            ts = (TransactionService) sm.getTransactionService();
            tm = ts.getTransactionManager();
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get the Transaction service: " + e);
            throw new ServiceException("Cannot get the Transaction service: ", e);
        }

        // Get the JMX Server via JMX Service
        try {
            mbeanServer =
                ((JmxService) ServiceManager.getInstance().getJmxService()).getJmxServer();
        } catch (Exception e) {
            // the JMX service may not be started
            mbeanServer = null;
        }

        // Init the XML parsing mode to no validation
        String parsingMode = "false";
        try {
            parsingMode = (String) ctx.lookup(PARSINGWITHVALIDATION);
        } catch (NamingException e) {
            // No problem if there is no value for 'parsingwithvalidation' (false by default)
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No parsingwithvalidation value specified in context");
            }
        }
        if ("false".equalsIgnoreCase(parsingMode)) {
            RarManagerWrapper.setParsingWithValidation(false);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "XML parsing without validation");
            }
        } else {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "XML parsing with validation");
            }
        }

        // Get the WorkManager defined in the EJB Service, if it exists
        EJBService ejbService = null;
        try {
            ejbService = (EJBService) ServiceManager.getInstance().getEjbService();
            workMgr = ejbService.getWorkManager();
        } catch (Exception e) {
            // the EJB service may not be started
            workMgr = null;
        }

        // Get the parameters for the WorkManager
        int execTime = 0;
        try {
            String etime = (String) ctx.lookup(EXECTIMEOUT);
            execTime = (new Integer(etime)).intValue();
        } catch (NamingException e) {
            // No problem if there is no value --> default value
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No exectimeout value specified in context, usually for client container");
            }
        }

        // Thread wait timeout
        int threadWaitTimeout = DEF_WRK_THREADWAITTIMEOUT;
        try {
            String tTimeout = (String) ctx.lookup(THREADWAITTIMEOUT);
            threadWaitTimeout = (new Integer(tTimeout)).intValue();
            if (threadWaitTimeout <= 0) {
                threadWaitTimeout = DEF_WRK_THREADWAITTIMEOUT;
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Resetting thread wait timeout to " + DEF_WRK_THREADWAITTIMEOUT);
                }
            }
        } catch (Exception e) {
            // default value will be used.
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No workthread wait timeout value specified in context");
            }
        }

        // MIN thread pool size
        int minWorkThreads = DEF_WRK_THREADS;
        try {
            String wThreads = (String) ctx.lookup(MINWORKTHREADS);
            minWorkThreads = (new Integer(wThreads)).intValue();
            if (minWorkThreads <= 0) {
                minWorkThreads = DEF_WRK_THREADS;
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Resetting min threads to " + DEF_WRK_THREADS);
                }
            }
        } catch (Exception e) {
            // default value will be used.
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No min workthreads value specified in context");
            }
        }

        // MAX thread pool size
        int maxWorkThreads = DEF_MAX_WRK_THREADS;
        try {
            String wThreads = (String) ctx.lookup(MAXWORKTHREADS);
            maxWorkThreads = (new Integer(wThreads)).intValue();
        } catch (Exception e) {
            // default value will be used.
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No max workthreads value specified in context");
            }
        }

        // Create WorkManager if we cannot use the one defined in ejb
        if (maxWorkThreads > 0 || workMgr == null) {
            logger.log(BasicLevel.DEBUG, "Create a WorkManager for Resources");
            if (maxWorkThreads <= 0) {
                maxWorkThreads = DEF_MAX_WRK_THREADS;
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Resetting max threads to " + DEF_WRK_THREADS);
                }
            }
            workMgr = new JWorkManager(minWorkThreads, maxWorkThreads, tm, threadWaitTimeout);
        }

        // Create BootstrapContext
        XATerminator xat = null;
        try {
            xat = ts.getCurrent().getXATerminator();
        } catch (Exception ex) {
            logger.log(BasicLevel.ERROR, "Unable to get an XATerminator from the TransactionService");
            throw new ServiceException("Unable to get an XATerminator from the TransactionService", ex);
        }
        bootCtx = new ResourceBootstrapContext(workMgr, xat);

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "ResourceService initialized");
        }
    }

    /**
     * Start the Resource service.
     * @throws ServiceException if the startup failed.
     */
    public void doStart() throws ServiceException {
        // creates each resource
        String rarFileName = null;
        CompNamingContext ctx = null;
        for (int i = 0; i < resourceNames.size(); i++) {
            rarFileName = (String) resourceNames.elementAt(i);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "rarFileName=" + rarFileName);
            }
            try {
                ctx = new CompNamingContext(rarFileName);
                ctx.rebind("rarFileName", rarFileName);
                ctx.rebind("isInEar", new Boolean(false));
                ctx.rebind("classloader", appsClassLoader);
                createResourceAdapter(ctx);
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "JOnAS: Cannot create resource: " + rarFileName + " exception: " + e);
                e.printStackTrace();
            }
        }

        // process any delayed rars
        if (!delayedRAs.isEmpty()) {
          try {
            processingDelayed = true;
            Object [] rList = null;
            rList = delayedRAs.toArray();
            for (int i = 0; i < rList.length; i++) {
                try {
                    createResourceAdapter((CompNamingContext) rList[i]);
                } catch (Exception e) {
                    e.printStackTrace();
                    String rFile = (String) ((CompNamingContext) rList[i]).lookup("rarFileName");
                    logger.log(BasicLevel.ERROR, "JOnAS: Cannot create resource: " + rFile + " exception: " + e);
                    logger.log(BasicLevel.ERROR, "Please verify that the rarlink is correct/deployed");
                }
            }
          } catch (Exception e) {
             e.printStackTrace();
             logger.log(BasicLevel.ERROR, "ResourceService: Error with delayed RAR file deployment\n" + e);
             throw new ServiceException("ResourceService: Error with delayed RAR file deployment", e);
          }
        }

        // Create and register the Resource Service MBean
        if (mbeanServer != null) {
            try {
                mbeanServer.registerMBean(this, JonasObjectName.resourceService());
            } catch (InstanceAlreadyExistsException iae) {
                logger.log(BasicLevel.ERROR, "Cannot start the Resource Service Already Exists:\n" + iae);
                throw new ServiceException("Cannot start the Resource Service Already Exists", iae);
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "ResourceService: Cannot start the Resource service:\n" + e);
                throw new ServiceException("ResourceService: Cannot start the Resource service", e);
            }

            // Create and register the archive configuration MBeans
            try {
               ManagedBean oManaged = JonasMBeanTools.getRegistry().findManagedBean("ArchiveConfigMBean");
               ModelMBean oMBean = oManaged.createMBean(new ArchiveConfigMBean());
                mbeanServer.registerMBean(oMBean, JonasObjectName.ArchiveConfig());

                oManaged = JonasMBeanTools.getRegistry().findManagedBean("RarConfigMBean");
                oMBean = oManaged.createMBean(new RarConfigMBean());
                mbeanServer.registerMBean(oMBean, JonasObjectName.RarConfig());
            } catch (Exception e) {
                logger.log(BasicLevel.WARN, "Could not register Archive Configuration MBean");
                e.printStackTrace();
            }
        }

    }

    /**
     * Stop the Resource service.
     * @throws ServiceException if the stop failed.
     */
    public void doStop() throws ServiceException {

        ServiceException se = null;
        synchronized (Rar.fileName2RA) {
            Enumeration keys = Rar.fileName2RA.elements();
            while (keys.hasMoreElements()) {
                Rar ra = (Rar) keys.nextElement();
                try {
                    ra.unRegister();
                    Rar.fileName2RA.remove(ra);
                } catch (Exception ex) {
                    logger.log(BasicLevel.ERROR, "ResourceService: Received the following:" + ex);
                    ex.printStackTrace();
                    if (se == null) {
                        se = new ServiceException(ex.getMessage());
                    }
                }
            }
        }
        // unregister resource MBeans
        if (mbeanServer != null) {
            try {
                // unregister Archive Config MBeans
                mbeanServer.unregisterMBean(JonasObjectName.ArchiveConfig());
                mbeanServer.unregisterMBean(JonasObjectName.RarConfig());
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "ResourceService: Cannot stop the Archive Config MBean:\n" + e);
            }

            try {
                // unregister resource Service MBean
                mbeanServer.unregisterMBean(JonasObjectName.resourceService());
            } catch  (MBeanRegistrationException mr) {
                logger.log(BasicLevel.ERROR, "Cannot cleanly stop the ResourceService: "
                                             + mr.getMessage());
            } catch  (InstanceNotFoundException infe) {
                logger.log(BasicLevel.ERROR, "Cannot cleanly stop the ResourceService: "
                                             + infe.getMessage());
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "ResourceService: Cannot stop the Resource service:\n" + e);
                throw new ServiceException("ResourceService: Cannot stop the Resource service", e);
            }
        }

        if (se != null) {
            throw se;
        }
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "ResourceService stopped");
        }
    }


    // IMPLEMENTATION OF 'ResourceService' INTERFACE //

    /**
     * Create a new resource adapter.  This Resource Adapter is configured via
     * xml files in the rar file
     * @param ctx Context to use for deploying an RAR
     * @return Sting resource objectName
     * @throws Exception error encountered
     */
    public String createResourceAdapter(Context ctx) throws Exception {

        // Parameters :
        // rarFileName, isInEar, classloader and possible AltDD and earUrl
        String rarFileName;
        try {
            rarFileName = (String) ctx.lookup("rarFileName");
            ctx.rebind("deployed", new Boolean(false));
        } catch (Exception ex) {
            String err = "Error while getting parameter from context param.";
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            throw new ResourceServiceException(err, ex);
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, rarFileName);
        }
        if (!rarFileName.endsWith(".rar")) {
            rarFileName += ".rar";
            ctx.rebind("rarFileName", rarFileName);
        }

        // Determine if the RAR file exists
        File f = new File(rarFileName);
        if (!f.exists()) {
            boolean found = false;
            String resFileName = null;
            // In case of the name is a rar file name, check also in
            // the JONAS_BASE/rars directory
            resFileName = RARSDIR + File.separator + rarFileName;
            f = new File(resFileName);
            found = f.exists();
            if (found) {
                rarFileName = resFileName;
                ctx.rebind("rarFileName", rarFileName);
            } else {
                logger.log(BasicLevel.ERROR, "createResourceAdapter: " + resFileName + " not found");
                Exception e = new NamingException(resFileName + " not found");
                throw e;
            }
        }

        URL rarUrl = f.toURL();

        Rar rar = new Rar(ctx, getDomainName(), getJonasServerName(), workMgr, bootCtx);

        try {
            Context ctxRar = rar.processRar();
        } catch (Exception ex) {
            // Exception error in processing unregister
            String err = "Error processing Rar: " + ex.getMessage();
            try {
                rar.unRegister();
            } catch (Exception exc) {
                err = err + "  Unregister also failed with " + exc.getMessage();
            }
            if (ex.getMessage() != null && ex.getMessage().indexOf("no jonas-ra.xml") > 0) {
                throw new ResourceServiceException("", ex);
            }
            logger.log(BasicLevel.ERROR, err);
            throw new ResourceServiceException(err, ex);
        }

        boolean isDeployed = false;
        try {
            isDeployed = ((Boolean) ctx.lookup("deployed")).booleanValue();
        } catch (Exception ex) {
            String err = "Error while getting parameter(isDeployed) from context param.";
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            throw new ResourceServiceException(err, ex);
        }

        if (!isDeployed) {
            // If isDeployed in not set, then an rar-link was specified that is not deployed and if
            // we are already processing the delayed rar files, then throw an exception otherwise
            // update the delayedRAs vector and process it again later.
            if (processingDelayed) {
                logger.log(BasicLevel.ERROR, "ResourceService.createRA: Resource (" + rarFileName + ") contains an invalid rarlink.");
                throw new ResourceServiceException("resource input file incorrect: invalid rarlink");
            }
            delayedRAs.add(ctx);
            return null;
        }

        Vector jNames = rar.getJndinames();
        if (jNames != null) {
            for (int i = 0; i < jNames.size(); i++) {
                Rar.jndiName2RA.put(jNames.get(i), rar);
            }
        }

        // Processed and deployed rar, so add it to our lists
        Rar.fileName2RA.put(rarUrl.getPath(), rar);

        String onRar = null;
        try {
            onRar = (String) ctx.lookup("onRar");
        } catch (Exception ex) {
            String err = "Error while getting parameter(onRar) from context param.";
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            throw new ResourceServiceException(err, ex);
        }

        return onRar.toString();
    }


    /**
     * Deploy the given rars of an ear file with the specified parent
     * classloader (ear classloader). (This method is only used for
     * for ear applications).
     * @param ctx the context containing the configuration
     * to deploy the rars.<BR>
     * This context contains the following parameters :<BR>
     *    - urls the list of the urls of the rars to deploy.<BR>
     *    - earRootURL the URL of the ear application file.<BR>
     *    - earClassLoader the ear classLoader of the j2ee app.<BR>
     *    - altDDs the optional URI of deployment descriptor.<BR>
     * @throws ResourceServiceException if an error occurs during
     * the deployment.
     */
    public void deployRars(Context ctx) throws ResourceServiceException {

        // Gets the parameters from the context :
        //    - urls the list of the urls of the rars to deploy.
        //    - earRootURL the URL of the ear application file.
        //    - earClassLoader the ear classLoader of the j2ee app.
        //    - altDDs the optional URI of deployment descriptor.
        URL[] urls = null;
        URL earUrl = null;
        ClassLoader earClassLoader = null;
        URL[] altDDs = null;
        try {
            urls = (URL[]) ctx.lookup("urls");
            earUrl = (URL) ctx.lookup("earUrl");
            earClassLoader = (ClassLoader) ctx.lookup("earClassLoader");
            altDDs = (URL[]) ctx.lookup("altDDs");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param ";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new ResourceServiceException(err, e);
        }

        delayedRAs.clear(); // reset the delayed RAs
        // Deploy all the rars of the ear application.
        for (int i = 0; i < urls.length; i++) {
            // Get the name of a rar to deploy.
            String fileName = urls[i].getFile();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Deploy rar '" + fileName + "' for the ear service");
            }

            // The context to give for the creation of the container
            // associated to the ejb-jar.
            Context contctx = null;
            try {
                contctx = new CompNamingContext(fileName);
                contctx.rebind("rarFileName", fileName);
                contctx.rebind("isInEar", new Boolean(true));
                contctx.rebind("earUrl", earUrl);
                if (altDDs[i] != null) {
                    contctx.rebind("altDD", altDDs[i]);
                }
                contctx.rebind("classloader", earClassLoader);
                createResourceAdapter(contctx);
            } catch (Exception e) {
                // A rar is corrupted so undeploy all the deployed rar
                // of the ear application.
                logger.log(BasicLevel.ERROR, "Error when deploying '" + fileName + "'");
                logger.log(BasicLevel.ERROR, e.getMessage());
                logger.log(BasicLevel.ERROR, "Undeploy rar of the ear application");

                for (int j = 0; j < i; j++) {
                    String rarFileName = urls[j].getFile();
                    try {
                        // Try to undeploy a rar of the ear application.
                        CompNamingContext compctx = new CompNamingContext(rarFileName);
                        compctx.rebind("rarFileName", rarFileName);
                        compctx.rebind("isInEar", new Boolean(true));
                        contctx.rebind("earUrl", earUrl);
                        unRegisterRar(compctx);
                    } catch (Exception ex) {
                        // Cannot undeploy a rar of the ear application
                        // So there is an error message.
                        logger.log(BasicLevel.ERROR, "Error when undeploying '" + rarFileName + "'");
                        logger.log(BasicLevel.ERROR, ex.getMessage());
                        logger.log(BasicLevel.ERROR, "Cannot undeploy rar of the ear application");
                    }

                }
                throw new ResourceServiceException("Error during the deployment", e);
            }
        }
        // process any delayed rars
        if (!delayedRAs.isEmpty()) {
          try {
            processingDelayed = true;
            Object [] rList = null;
            rList = delayedRAs.toArray();
            for (int i = 0; i < rList.length; i++) {
                try {
                    createResourceAdapter((CompNamingContext) rList[i]);
                } catch (Exception e) {
                    logger.log(BasicLevel.ERROR, "JOnAS: Cannot create resource: " + (String) rList[i] + " exception: " + e);
                    logger.log(BasicLevel.ERROR, "Please verify that the rarlink is correct");
                    e.printStackTrace();
                }
            }
          } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "ResourceService: Error with delayed RAR file deployment\n" + e);
            throw new ServiceException("ResourceService: Error with delayed RAR file deployment", e);
          }
        }
    }


    /**
     * Undeploy the given rars of an ear file. (This method is only
     * used for the ear applications).
     * @param urls the list of the urls of the rars to undeploy.
     * @param earUrl the URL of the associated EAR file
     */
    public void unDeployRars(URL[] urls, URL earUrl) {
        for (int i = 0; i < urls.length; i++) {
            String fileName = urls[i].getFile();
            if (Rar.fileName2RA.containsKey(fileName)) {
                try {
                    // Try to undeploy a rar of the ear application.
                    CompNamingContext compctx = new CompNamingContext(fileName);
                    compctx.rebind("rarFileName", fileName);
                    compctx.rebind("isInEar", new Boolean(true));
                    compctx.rebind("earUrl", earUrl);
                    unRegisterRar(compctx);
                } catch (Exception ex) {
                    logger.log(BasicLevel.ERROR, "Cannot undeploy resource: " + fileName + " " + ex);
                }
            } else {
                logger.log(BasicLevel.ERROR, "Cannot remove the non-existant rar '" + fileName + "'");
            }
        }
    }


    /**
     * Unregister the resource adapter.
     * @param ctx Context to use for unregistering an RAR
     * @throws Exception error encountered
     */
    public void unRegisterRar(Context ctx) throws Exception {
        String rarFileName;
        try {
            rarFileName = (String) ctx.lookup("rarFileName");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param.";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new ResourceServiceException(err, e);
        } catch (Exception ex) {
            String err = "Error while getting parameter from context param.";
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            throw new ResourceServiceException(err, ex);
        }

        if (manageLogger.isLoggable(BasicLevel.DEBUG)) {
            manageLogger.log(BasicLevel.DEBUG, "TEST Unregister MBeans for RAR in file: " + rarFileName);
        }

        String rarPath = (new File(rarFileName)).toURL().getPath();
        Rar rar = (Rar) Rar.fileName2RA.get(rarPath);
        Vector jNames = rar.getJndinames();
        rar.unRegister();

        // Remove rars from hashtables
        for (int i = 0; i < jNames.size(); i++) {
            Rar.jndiName2RA.remove(jNames.get(i));
        }
        Rar.fileName2RA.remove(rarPath);
        resourceNames.remove(normalizePath(rarFileName));

        logger.log(BasicLevel.INFO,
                    "ResourceService: unRegisterRar: " + rarFileName);
    }

    /**
     * Return the JDBC ResourceAdapter MBean ObjectNames deployed in the current server.
     * The JDBC ResourceAdapters have a 'properties' attribue containing the following properties
     * set (not null and not empty): 'dsClass', 'URL'.
     * @return The found MBean ObjectNames or null if no JDBC ResourceAdapter MBean registered for
     * the current server in the current domain.
     * @throws Exception The ResourceAdapter MBeans checking failed.
     */
    public ObjectName[] getJDBCResourceAdapaters() throws Exception {
        ObjectName[] result = null;
        ArrayList al = null;
         ObjectName raOns = J2eeObjectName.getResourceAdapters(getDomainName(), getJonasServerName());
         Set ons = mbeanServer.queryNames(raOns, null);
         if (ons.isEmpty()) {
             return null;
         }
         // Got ResourceAdapters for this server
         al = new ArrayList();
         Iterator it = ons.iterator();
         for (; it.hasNext();) {
             ObjectName aRaOn = (ObjectName) it.next();
                 Properties props = (Properties) mbeanServer.getAttribute(aRaOn, "properties");
                 String dsClassValue = props.getProperty("dsClass");
                 String urlValue = props.getProperty("URL");
                 if (dsClassValue != null
                         && dsClassValue.length() != 0
                         && urlValue != null
                         && urlValue.length() != 0) {
                     // Thsis is a well configured JDBC ResourceAdapter
                     al.add(aRaOn);
                 }
         }
         int nbJDBCResourceAdapaters = al.size();
         if (nbJDBCResourceAdapaters == 0) {
             return null;
         } else {
             result = new ObjectName[nbJDBCResourceAdapaters];
             for (int i = 0; i < nbJDBCResourceAdapaters; i++) {
                 result[i] = (ObjectName) al.get(i);
             }
         }
         return result;
    }

    /**
     * Return the JDBC ResourceAdapter MBean OBJECT_NAME deployed in the current server
     * haveing the 'jndiName' attribue value equal to the given jndiName
     * @param jndiName A DataSource jndi name we are looking for.
     * @return The found MBean OBJECT_NAME or null if none of the JDBC ResourceAdapter MBean
     * have the given jndi name.
     * @throws Exception The ResourceAdapter MBeans checking failed.
     */
    public String getJDBCResourceAdapater(String jndiName) throws Exception {
        String result = null;
        ObjectName[] raOns = getJDBCResourceAdapaters();
        ObjectName raOn = null;
        String raJndiName = null;
        if (raOns != null) {
            for (int i = 0; i < raOns.length; i++) {
                raOn = raOns[i];
                raJndiName = (String) mbeanServer.getAttribute(raOn, "jndiName");
                if (jndiName.equals(raJndiName)) {
                    // found the JDBC RA MBean having the givent jndi name
                    return raOn.toString();
                }
            }
        }
        return result;
    }

    //--------------------------------------------------------------
    //           MBean Methods
    //--------------------------------------------------------------

    /**
     * @return Integer Total Number of Resources available in JOnAS
     */
    public Integer getCurrentNumberOfResource() {
        return getCurrentNumberOfRars();
    }


    /**
     * @return Integer Total Number of Rars available in JOnAS
     */
    public Integer getCurrentNumberOfRars() {
        return new Integer(Rar.fileName2RA.size());
    }


    /**
     * @return the list of RAR files deployed
     */
    public List getDeployedRars() {
        ArrayList al = new ArrayList();
        String rarFileName = null;
        Enumeration keys = Rar.fileName2RA.keys();
        while (keys.hasMoreElements()) {
            rarFileName = keys.nextElement().toString();
            try {
                al.add((new File(rarFileName)).toURL().getPath());
            } catch (Exception e) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Unable to add rarfile " + rarFileName + " to arraylist");
                }
            }
        }
        return al;
    }

    /**
     * Return the list of installed RAR containers.
     * The RAR files or the directories with expanded RAR container are searched
     * in JONAS_BASE/rars and all rar directories 'autoload'.
     * @return The list of RAR files or the directories with expanded RAR container found
     * @throws Exception if the list can't be retrieved
     */
    public List getInstalledRars() throws Exception {
        // get JAR files found in JONAS_BASE/rars
        ArrayList al = JModule.getInstalledContainersInDir(RARSDIR, JModule.RAR_EXTENSION
            , JModule.RAR_CHILD_DIR, JModule.RAR_CONFIRM_FILE);
        // get RAR files found in all autoload directories
        for (int i = 0; i < autoNames.size(); i++) {
            al.addAll(JModule.getInstalledContainersInDir(autoNames.get(i).toString(),
                JModule.RAR_EXTENSION, JModule.RAR_CHILD_DIR, JModule.RAR_CONFIRM_FILE));
        }
        return al;
    }

    /**
     * This method is added temporarily. It will disapear when Rars will have their associated MBeans
     * (when Rars will become manageable)
     * @return the names of the rars currently deployed in the JOnAS server
     */
    public Set getRarNames() {
        HashSet names = new HashSet();
        String rarFileName = null;
        for (int i = 0; i < resourceNames.size(); i++) {
            rarFileName = (String) resourceNames.elementAt(i);
            names.add(rarFileName);
        }
        return names;
    }


    /**
     * Deploy an RAR by delegating the operation to the createResourceAdapter method.
     * This is used for JMX management.
     * @param fileName the fileName of the rar which must be be deployed.
     * @return The ObjectName of the MBean associated to the deployed J2EE Application
     * @throws RemoteException if rmi call failed.
     * @throws ResourceServiceException if the deployment of the RAR failed.
     */
    public String deployRarMBean(String fileName) throws RemoteException, ResourceServiceException {
        Context ctx = null;
        String onRar = null;
        try {
            ctx = new CompNamingContext(fileName);
            ctx.rebind("rarFileName", fileName);
            ctx.rebind("isInEar", new Boolean(false));
            ctx.rebind("classloader", appsClassLoader);
            onRar = createResourceAdapter(ctx);
        } catch (Exception e) {
            String err = "Error when deploying the rar file: " + fileName;
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            e.printStackTrace();
            throw new ResourceServiceException(err, e);
        }

        return onRar;
    }

    /**
     * Deploy the resource adapter
     * @param fileName the name of the rar file.
     * @throws Exception if unable to deploy the rar
     * @return String ObjectName of the deployed rar
     */
    public String deployRar(String fileName) throws Exception {
        return deployRarMBean(fileName);
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the rar file.
     * @return true if the rar is deployed, else false.
     */
    public Boolean isRarDeployed(String fileName) {
        return new Boolean(isRarLoaded(fileName));
    }

    /**
     * Test if the specified unpack name is already deployed or not. This
     * method is defined in the ResourceService interface.
     * @param unpackName the name of the rar file.
     * @return true if the rar is deployed, else false.
     */
    public boolean isRarDeployedByUnpackName(String unpackName) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "entering for unpackName= " + unpackName);
        }
/*        // for each rar loaded
        Enumeration lc = ears.elements();
        while (lc.hasMoreElements()) {
            Ear ear = (Ear) lc.nextElement();

            // get unpack name of the ear
            String deployedUnpackName = new File(ear.getUnpackName()).getName() ;
            logger.log(BasicLevel.DEBUG, "deployedUnpackName=" + deployedUnpackName);

            if (deployedUnpackName.equals(unpackName)) {
                return true;
            }
            // else, go to the next loop
        }
        // not found
*/
        return false;
    }

    /**
     * Undeploy an RAR by delegating the operation to the unRegisterRar() method.
     * This is used for JMX management.
     * @param fileName the fileName of the rar which must be be undeployed.
     * @throws RemoteException if rmi call failed.
     * @throws ResourceServiceException if the undeployment of the RAR failed.
     */
    public void unDeployRarMBean(String fileName) throws RemoteException, ResourceServiceException {
        /* We have only the name of the file, not its associated path, so we look in
           the current directory and in the rar applications directory
        */

        boolean found = true;
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Trying to undeploy: " + fileName
                    + " with the following deployed:" + Rar.fileName2RA);
        }

        try {
            // Determine if the RAR is in list
            File f = new File(fileName);
            if (!Rar.fileName2RA.containsKey(f.toURL().getPath())) {
                found = false;
                // Check also in the JONAS_BASE/rars directory
                String resFileName = RARSDIR + File.separator + fileName;
                f = new File(resFileName);
                if (Rar.fileName2RA.containsKey(f.toURL().getPath())) {
                    fileName = resFileName;
                    found = true;
                }
            }

            if (!found) {
                String err = "Cannot undeploy the rar '" + fileName + "', it is not deployed.";
                logger.log(BasicLevel.ERROR, err);
                throw new ResourceServiceException(err);
            }
        } catch (Exception ex) {
            String err = "Error trying to undeployRarMBean " + fileName;
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            throw new ResourceServiceException(err, ex);
        }

        //We've got the file, now we bind the params
        CompNamingContext compctx = null;
        try {
            compctx = new CompNamingContext(fileName);
            compctx.rebind("rarFileName", fileName);
            compctx.rebind("isInEar", new Boolean(false));
            //Call the function
            unRegisterRar(compctx);
        } catch (NamingException e) {
            String err = "Error when binding parameters";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new ResourceServiceException(err, e);
        } catch (Exception ex) {
            String err = "Error when unRegistering rar " + fileName;
            logger.log(BasicLevel.ERROR, err + ex.getMessage());
            ex.printStackTrace();
            throw new ResourceServiceException(err, ex);
        }

    }

    /**
     * Undeploy the resource adapter
     * @param fileName the name of the rar file.
     * @throws Exception if not able to undeploy the rar
     */
    public void unDeployRar(String fileName) throws Exception {
        unDeployRarMBean(fileName);
    }

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

        String updateName = null;
        boolean isLoaded = false;
        try {
            if (Rar.fileName2RA.containsKey(fileName)) {
                isLoaded = true;
            }
            // If not found check the exact filename
            if (!isLoaded) {
                // construct name for rars in JONAS_BASE/rars directory
                updateName = RARSDIR + File.separator + fileName;
                File f = new File(updateName);
                URL rarUrl = f.toURL();

                //Check if the rar is already deployed or not
                if (Rar.fileName2RA.containsKey(rarUrl.getPath())) {
                    isLoaded = true;
                }
            }
        } catch (Exception e) {
            String err = "Cannot determine if the rar is deployed or not";
            logger.log(BasicLevel.ERROR, err);
            return false;
        }

        return isLoaded;
    }


    /**
     * Return the list of installed RAR container ready to deploy.
     *
     * @return The list of deployable RAR container
     * @throws Exception if error retrieving the list
     */
    public List getDeployableRars() throws Exception {
        List al = getInstalledRars();
        al.removeAll(getDeployedRars());
        return al;
    }

    /**
     * Return the list of "autoload" directories for RAR containers.
     * @return The list of all "autoload" directories
     */
    public List getAutoloadDirectories() {
        ArrayList al = new ArrayList();
        for (int i = 0; i < autoNames.size(); i++) {
            try {
                al.add((new File(autoNames.get(i).toString())).toURL().getPath());
            } catch (Exception e) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Can't get autoload directories : " + e.getMessage());
                }
            }
        }
        return al;
    }

    /**
     * Return the Rars directory.
     * @return The Rars directory
     */
    public String getRarsDirectory() {
        String sRet = null;
        try {
            sRet = (new File(RARSDIR)).toURL().getPath();
        } catch (Exception e) {
            throw new RuntimeException("Cannot get the RARS directory", e);
        }
        return sRet;
    }

    //--------------------------------------------------------------
    //           Private Methods
    //--------------------------------------------------------------

    /**
     * Add recursively the rars of the specified directory.
     * If the dir has a relative path, this path is relative from where
     * the Application Server is launched. If the dir is not found it will
     * be searched in $JONAS_BASE/rars/ directory.
     * @param dirPath the path to the directory containing the rars to
     * load.
     */
    private void addRars(String dirPath) {
        boolean found = false;

        // Look the directory relative to the $JONAS_BASE/rars directory
        File dir = new File(RARSDIR + File.separator + dirPath);
        found = dir.isDirectory();

        if (found) {
            autoNames.add(dir);
            addRarsFrom(dir);
        } else {
            String err = "Warning: Cannot load dir: '" + dirPath + "' ";
            err += "is not a directory or directory doesn't exist";
            logger.log(BasicLevel.WARN, err);
        }
    }

    /**
     * Add the rars of the specified directory.
     * @param dir the directory from which the rars are loaded.
     * @throws ResourceServiceException if the argument is not a directory
     */
    private void addRarsFrom(File dir) throws ResourceServiceException {
        try {
            if (dir.isDirectory()) {
                File[] files = dir.listFiles();
                for (int i = 0; i < files.length; i++) {
                    if (files[i].isFile()) {
                        if (files[i].getPath().toLowerCase().endsWith(".rar")) {
                            resourceNames.add(files[i].getCanonicalPath());
                        }
                    } else {
                        autoNames.add(files[i]);
                        addRarsFrom(files[i]);
                    }
                }
            } else {
                String err = "Cannot load dir: '" + dir.getPath();
                err += "' is not a directory";
                logger.log(BasicLevel.ERROR, err);
                throw new ResourceServiceException(err);
            }
        } catch (IOException e) {
            String err = "Invalid file name '" + dir.getPath();
            logger.log(BasicLevel.ERROR, err);
            throw new ResourceServiceException(err, e);
        }
    }

    /**
     * Normalize the path for the platform
     * @param path the path to normalize
     * @return String normalized path
     */
    private String normalizePath(String path) {
        String ret = null;
        if (File.separatorChar == '/') {
            ret = path.replace('\\', File.separatorChar);
        } else {
            ret = path.replace('/', File.separatorChar);
        }
        return ret;
    }
}


