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

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Set;
import java.util.Iterator;

import javax.management.MalformedObjectNameException;
import javax.management.MBeanRegistrationException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;

import javax.resource.Referenceable;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;

import javax.transaction.TransactionManager;

import org.objectweb.jonas.common.JProp; 
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.jtm.TransactionService;
import org.objectweb.jonas.naming.CompNamingContext;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;

import org.objectweb.transaction.api.ResourceManagerEventListener;
import org.objectweb.transaction.ramanager.ConnectionManagerImpl;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * JCA resource service implmentation
 * @author Philippe Coq
 * Contributor(s):
 * JOnAS 2.4 Sebastien Chassande-Barrioz (sebastien.chassande@inrialpes.fr)
 *
 */
public class ResourceServiceImpl extends AbsServiceImpl implements 
    ResourceService,
    ResourceServiceImplMBean {

    static private Logger logger = null;
    
    // Properties for inits
    public final String MANAGED_CONNECTION_FACTORY_CLASS 
        = "managedconnectionfactoryclass";
    public static final String JNDI_NAME = "jndiname";
    public static final String RESOURCE_LIST = "resources";
    public static final String PRINTWRITER ="printWriter";
    public static final String MONITOR_FACTORY 
        = "org.objectweb.monolog.MonitorFactory";

    private class RAInfo {
        public Object cf;
        public ManagedConnectionFactory mcf;
        public ConnectionManager cm;
        public RAInfo(Object _cf, ManagedConnectionFactory _mcf,
                      ConnectionManager _cm) {
            cf = _cf;
            mcf = _mcf;
            cm = _cm;
        }
    }

    /**
     * This hashtable associates a jndiName to an instance of the local
     * class RAInfo which contains a reference to the connectionFactory
     * and the ManagedConnectionFactory
     */
    static private Hashtable jndiName2RA = new Hashtable(); 
    
    /**
     * The transaction manager in server
     */
    private TransactionManager tm = null ;
   
    /**
     * The ConnectionManager manages connections of resource adapters
     * and more exactly the association between logical connections,
     * physical connections, local transactions and global transaction(XA)
     */
    private ConnectionManagerImpl cm = null;

    /**
     * JNDI Context
     */
    private Context ictx = null;


    private Vector resourceNames = null;

    //IMPLEMENTATION OF 'AbsServiceImpl' ABSTRACT CLASS

    /**
     * - 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
     * - Instanciate the connection manager
     */
    public void doInit(Context ctx) {
        if(logger == null)
            logger = Log.getLogger(Log.JONAS_JCA_PREFIX);
        resourceNames = new Vector();
                
        // Avoid using NamingManager here: performance is not a goal here.
        try {
            ictx = new InitialContext();
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR,"Cannot create initial context when Resource service initializing");
            throw new ServiceException("Cannot create initial context when Resource service initializing",e);
        }

        // Get the list of the resource adapter names

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

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

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

    public void doStart() throws ServiceException {
        // creates each resource
        String resName = null;
        for (int i = 0; i < resourceNames.size(); i++) {
            resName = (String) resourceNames.elementAt(i);
            logger.log(BasicLevel.DEBUG, "resName="+resName);
            try {
                createResourceAdapter(resName, createConnectionManager());
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR,"JOnAS: Cannot create resource: "+resName+" exception: "+e);
                logger.log(BasicLevel.ERROR,"Please check if "+resName+".properties is available");
            }
        }

        try {
            // Register ResourceService MBean 
            ((JmxService)ServiceManager.getJmxService()).getJmxServer().registerMBean(this,
JonasObjectName.resourceService());
        } catch (ServiceException se) {
            // Jmx Service not available, do nothing
        } 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);
        }

    }

    public void doStop() throws ServiceException {
        synchronized (jndiName2RA) {
            for (Enumeration e = jndiName2RA.keys(); e.hasMoreElements();) {
                String jndiName = (String) e.nextElement();
                RAInfo rai = (RAInfo) jndiName2RA.get(jndiName);
                try {
                    // Unbind from jndi
                    ictx.unbind(jndiName);
                } catch (NamingException ex) {
                    logger.log(BasicLevel.ERROR,"ResourceService: cannot unbind ResourceAdapter"
                                +jndiName+" :\n" + ex);
                    throw new ServiceException(ex.getMessage());
                }

                // The connection manger must stop to manage the RA
                //rai.cm.removeResourceAdapter();

                    
                try {
                    // unbind MBean
                    ((JmxService)ServiceManager.getJmxService()).getJmxServer()
                        .unregisterMBean( 
                                         JonasObjectName.resourceAdapter(jndiName));
                } catch  (MBeanRegistrationException mone) {
                    //???
                } catch  (MalformedObjectNameException mone) {
                    //???
                } catch  (InstanceNotFoundException infe) {
                    //???
                } catch  (ServiceException se) {
                    // Jmx Service not available, do nothing
                }

                logger.log(BasicLevel.DEBUG, 
                            "ResourceService: unbindResourceAdapter:"+jndiName);
            }
        }

        try {
            MBeanServer mbeanServer =
((JmxService)ServiceManager.getJmxService()).getJmxServer();
            // unregister resource Service MBean 
            mbeanServer.unregisterMBean(JonasObjectName.resourceService());
            // unregister all Resource Adaptor
            Set objectNames =
mbeanServer.queryNames(JonasObjectName.allResourceAdaptors(), null) ;
            Iterator it  = objectNames.iterator();
            while (it.hasNext()) {
                mbeanServer.unregisterMBean((ObjectName)it.next());
            }
        } catch (ServiceException se) {
            // Jmx Service not available, do nothing
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR,"EJBService: Cannot stop the Resource service:\n"+e);
            throw new ServiceException("ResourceService: Cannot stop the Resource service",e);
        }
        logger.log(BasicLevel.DEBUG, "ResourceService stopped");
    }


    private ConnectionManager createConnectionManager() throws Exception {

        ConnectionManagerImpl cm = new ConnectionManagerImpl();

        Context c = new CompNamingContext("");
        c.rebind(ConnectionManagerImpl.TRANSACTION_MANAGER, tm);
        c.rebind(ConnectionManagerImpl.RESOURCE_MANAGER_EVENT_LISTENER,tm);

        // Set the MonologFactory into context of initialisation
        c.rebind(ConnectionManagerImpl.LOGGER, logger);

        cm.init(c);
        return cm;
    }

    // IMPLEMENTATION OF 'ResourceService' INTERFACE //

    /*
     * Create a new resource adapter.This Resource Adapter is configured via 
     * properties found in resName.properties. In this properties file must be 
     * set mandatory properties and optional properties the mandatory properties
     * are:
     *  - jndiname :  the jndi name for registering the resource adapter
     *  - managedconnectionfactoryclass : the name of the
ManagedConnectionFactory
     *    implementation class
     */ 
    public void createResourceAdapter(String  resName, ConnectionManager cm)
throws Exception {
        logger.log(BasicLevel.DEBUG,"entering for "+resName+","+cm);
        // Load properties file
        Properties p = new JProp(resName).getFilesEnv();
        String mcfc = p.getProperty(MANAGED_CONNECTION_FACTORY_CLASS);
        if(mcfc == null){
            logger.log(BasicLevel.ERROR,"ResourceService.createRA:"+MANAGED_CONNECTION_FACTORY_CLASS+" property not found");
            throw new Exception("configuration file incorrect");
        }
        // Instanciate the ra : mcf class
        Class mcfClass = Class.forName(mcfc);
        p.remove(MANAGED_CONNECTION_FACTORY_CLASS);
        ManagedConnectionFactory mcf = 
            (ManagedConnectionFactory) mcfClass.newInstance();

        logger.log(BasicLevel.DEBUG, "mcf="+ mcf);

        // Obtain the CF and connect the RA to the ConnectionManager
        // A connection factory must implements javax.resource.Referenceable
        // Associate the resource adapter to the connectionmanager
        Referenceable cf = (Referenceable) 
            mcf.createConnectionFactory(cm);
        ((ConnectionManagerImpl) cm).setResourceAdapter(mcf);

        logger.log(BasicLevel.DEBUG, "cf="+ cf);

        // Get the jndi name of the ra
        String jndiName = p.getProperty(JNDI_NAME);
        p.remove(JNDI_NAME);
	mcf.setLogWriter(Log.getLogWriter("org.objectweb.jonas.jca"));

        logger.log(BasicLevel.DEBUG, "jndiName="+ jndiName);
        
        // init the ra with setter method
        // The property JNDI_NAME and MANAGED_CONNECTION_FACTORY_CLASS
        // must be removed from  the Properties variable.
        Enumeration enum = p.propertyNames();
        while ( enum.hasMoreElements() ){
            String fieldName = (String) enum.nextElement();
            String methodName = "set"+fieldName;
            Method[] m = mcfClass.getMethods();
            int i=0;
            while (i<m.length 
                   && (! m[i].getName().equals(methodName) 
                       || m[i].getParameterTypes().length!=1 )
                   ) {
                i++;
            }
            if (i<m.length) {
                Class [] paramtype = m[i].getParameterTypes();
                Object[] param = new Object[1];

                if (paramtype[0].equals(java.lang.Integer.TYPE) ||
                    paramtype[0].equals(java.lang.Integer.class)){
                    param[0]  = new Integer(p.getProperty(fieldName,""));

                } else if (paramtype[0].equals(java.lang.Boolean.TYPE) ||
                           paramtype[0].equals(java.lang.Boolean.class) ){
                    param[0]  = new Boolean(p.getProperty(fieldName,""));

                } else if (paramtype[0].equals(java.lang.Double.TYPE) || 
                           paramtype[0].equals(java.lang.Double.class) ){
                    param[0]  = new Double(p.getProperty(fieldName,""));

                } else if (paramtype[0].equals(java.lang.Byte.TYPE) ||
                           paramtype[0].equals(java.lang.Byte.class) ){
                    param[0]  = new Byte(p.getProperty(fieldName,""));
                        
                } else if (paramtype[0].equals(java.lang.Short.TYPE) ||
                           paramtype[0].equals(java.lang.Short.class)){
                    param[0]  = new Short(p.getProperty(fieldName,""));
                        
                } else if (paramtype[0].equals(java.lang.Long.TYPE) ||
                           paramtype[0].equals(java.lang.Long.class) ){
                    param[0]  = new Long(p.getProperty(fieldName,""));
                        
                } else if (paramtype[0].equals(java.lang.Float.TYPE) ||
                           paramtype[0].equals(java.lang.Float.class) ){
                    param[0]  = new Float(p.getProperty(fieldName,""));
                        
                } else if (paramtype[0].equals(java.lang.String.class)){
                    param[0]  = (String) p.getProperty(fieldName,"");
                        
                } else {
                    logger.log(BasicLevel.ERROR,"Type unsupported for setter method:"
                                +methodName);
                    throw new Exception("incorrect type for setter method ");
                }
                // invocation of the corresponding setter method on 
                // ManagedConnectionFactory instance
                try {
                    m[i].invoke(mcf,param);
                } catch (Exception e) {
                    logger.log(BasicLevel.DEBUG, "method "
                                +methodName+
                                " not found on resource adapter, check the configuration file "
                                +resName+".properties");
                }
            } else {
                logger.log(BasicLevel.ERROR,"Method not found: "+methodName);
                throw new Exception("incorrect method name");
            }
        }
        

        // Register into Jndi
        try {

            Reference ref = 
                new Reference(
                              cf.getClass().getName(),
                              ConnectionFactoryJNDIHandler.class.getName(),
                              null);
            ref.add( new StringRefAddr(JNDI_NAME, jndiName) );
            cf.setReference(ref);
            ictx.rebind(jndiName, cf);
            
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR,"ResourceService: Cannot register ResourceAdapter in naming with the name " + jndiName);
            logger.log(BasicLevel.ERROR,"ResourceService: Exception caught : "+e);
            return;
        }
        
        synchronized (jndiName2RA) {
            // Save the association (jndiName, RA)
            jndiName2RA.put(jndiName, new RAInfo(cf, mcf, cm) );
        }

        try {
            // MBean resourcesource registering
            ((JmxService)ServiceManager.getJmxService()).getJmxServer()
                .registerMBean(
                               new JmxResourceAdapter(p, jndiName), 
                               JonasObjectName.resourceAdapter(jndiName));
        } catch (ServiceException se) {
            // Jmx Service not available, do nothing
        }       
         logger.log(BasicLevel.WARN, "Mapping ResourceAdapter "+resName+ " on "+jndiName);

    }


    //--------------------------------------------------------------
    //           Other Method
    //--------------------------------------------------------------

    /**
     * get the ResourceAdapter  matching the raname
     * will be used in ConnectionFactoryJNDIHandler class at lookup time
     */
    public static Object getConnectionFactory(String jndiName) {
        synchronized (jndiName2RA) {
            RAInfo rai = (RAInfo) jndiName2RA.get(jndiName);
            return (rai==null ? null : rai.cf );
        }
    }

    /**
     * MBean method
     * @return Integer Total Number of Resourcesource available in JOnAS
     */ 
    public Integer getCurrentNumberOfResource() {
        return new Integer(jndiName2RA.size());
    }
}
