/**
 * EasyBeans
 * Copyright (C) 2009-2011 Bull S.A.S.
 * Contact: easybeans@ow2.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: EJBContainer.java 6246 2012-04-26 09:00:16Z benoitf $
 * --------------------------------------------------------------------------
 */

package javax.ejb.embeddable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.WeakHashMap;

import javax.ejb.EJBException;
import javax.ejb.spi.EJBContainerProvider;

/**
 * Used to execute an EJB application in an embeddable container.
 * @see <a href="http://www.jcp.org/en/jsr/detail?id=318">EJB 3.1 specification</a>
 * @author Florent Benoit
 * @since EJB 3.1 version.
 */

public abstract class EJBContainer {

    /**
     * The provider supplies the provider configuration file by creating a text file named
     * javax.ejb.spi.EJBContainerProvider and placing it in the META-INF/services directory of one of its JAR files.
     * The contents of the file should be the name of the provider implementation class of the
     * javax.ejb.spi.EJBContainerProvider interface.
     */
    private static final String EJBCONTAINER_PROVIDER_JAR_PROPERTY = "META-INF/services/" + EJBContainerProvider.class.getName();
    
    /**
     * Standard property name for specifying the application name of the EJB modules executing within the embeddable container.
     */
    public static final String APP_NAME = "javax.ejb.embeddable.appName";

    /**
     * Standard property name for specifying the embeddable container implementation bootstrap class.
     */
    public final static String PROVIDER = "javax.ejb.embeddable.provider";

    /**
     * Standard property name for specifying the set of modules to be initialized.
     */
    public static final String MODULES = "javax.ejb.embeddable.modules";

    /**
     * Map of EJBContainer providers between a given classloader and the list of EJBContainer providers.
     */
    private static WeakHashMap<ClassLoader, List<EJBContainerProvider>> ejbContainerProviders = new WeakHashMap<ClassLoader, List<EJBContainerProvider>>();

    /**
     * Shutdown an embeddable EJBContainer instance.
     */
    public abstract void close();

    /**
     * Create and initialize an embeddable EJB container.
     */
    public static EJBContainer createEJBContainer() {
        return createEJBContainer(null);
    }

    /**
     * Create and initialize an embeddable EJB container with a set of configuration properties.
     * @param properties
     * @return
     */
    public static EJBContainer createEJBContainer(java.util.Map<?, ?> properties) {
       
        List<EJBContainerProvider> ejbContainerProviders = getEJBContainerProviders();
        if (ejbContainerProviders == null || ejbContainerProviders.isEmpty()) {
            throw new EJBException("No EJB Container Provider provider found");
        }

        // Ask each provider if they can create a container for the given properties
        for (EJBContainerProvider provider : ejbContainerProviders) {
            EJBContainer ejbContainer = provider.createEJBContainer(properties);
            if (ejbContainer != null) {
                return ejbContainer;
            }
        }
        
        throw new EJBException("Unable to build any EJBContainer from providers + '" + ejbContainerProviders + "'.");
    }
    
    
    /**
     * Retrieve a naming context for looking up references to session beans executing in the embeddable container.
     * @return
     */
    public abstract javax.naming.Context getContext();


    /**
     * Initialize the list of EJB Container providers. (it not done).
     */
    private static void findEJBContainerProviders(ClassLoader classLoader) {

        // List of EJBCONTAINER_PROVIDER available in the given classloader.
        Enumeration<URL> urls = null;
        try {
            urls = classLoader.getResources(EJBCONTAINER_PROVIDER_JAR_PROPERTY);
        } catch (IOException e) {
            throw new EJBException("Cannot get resources named '" + EJBCONTAINER_PROVIDER_JAR_PROPERTY
                    + "' on the current classloader '" + classLoader + "'.", e);
        }

        // Analyze URLs
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URLConnection urlConnection = null;
            try {
                urlConnection = url.openConnection();
            } catch (IOException e) {
                throw new EJBException("Cannot open connection on URL '" + url + "'.", e);
            }

            // avoid lock
            urlConnection.setDefaultUseCaches(false);
            InputStream is = null;
            try {
                is = urlConnection.getInputStream();
                Reader reader = null;
                BufferedReader bufferedReader = null;
                try {
                    reader = new InputStreamReader(is);
                    bufferedReader = new BufferedReader(reader);
                    String line = bufferedReader.readLine();
                    if (line == null) {
                        throw new EJBException("No lines found in the file available at the URL '" + url + "'.");
                    }
                    // add The EJB Container provider found.
                    addEJBContainerProvider(classLoader, line.trim());
                } finally {
                    reader.close();
                    bufferedReader.close();
                }
            } catch (IOException e) {
                throw new EJBException("Cannot get InputStream on URL '" + url + "'.", e);
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        throw new EJBException("Cannot close InputStream on URL '" + url + "'.", e);
                    }
                }
            }

        }

    }
    
    /**
     * 
     * @return current EJB Containers associated to the current CL.
     */
    private static List<EJBContainerProvider> getEJBContainerProviders() {
        // Get current classloader
        ClassLoader currentCL = Thread.currentThread().getContextClassLoader();

        // Get current list of providers for the given classloader
        List<EJBContainerProvider> availableEJBContainers = null;
        synchronized (ejbContainerProviders) {
            availableEJBContainers = ejbContainerProviders.get(currentCL);
            if (availableEJBContainers == null) {
                findEJBContainerProviders(currentCL);
            }
            availableEJBContainers = ejbContainerProviders.get(currentCL);
        }
        return availableEJBContainers;

    }
    
    /**
     * Add to the set of EJB Container provider the given EJBContainer provider (by using its name).
     * @param ejbContainerProviderName name of the EJBContainer.
     */
    private static void addEJBContainerProvider(final ClassLoader classLoader, final String ejbContainerProviderName) {
        // load the class
        Class<?> ejbContainerProviderClass = null;
        try {
            ejbContainerProviderClass = classLoader.loadClass(ejbContainerProviderName);
        } catch (ClassNotFoundException e) {
            throw new EJBException("Cannot load the EJBContainer provider class with the name '"
                    + ejbContainerProviderName + "' in the ClassLoader '" + classLoader + "'.", e);
        }

        // build a new instance
        Object object = null;
        try {
            object = ejbContainerProviderClass.newInstance();
        } catch (InstantiationException e) {
            throw new EJBException("Cannot build an instance of the EJBContainer provider class with the name '"
                    + ejbContainerProviderName + "' in the ClassLoader '" + classLoader + "'.", e);
        } catch (IllegalAccessException e) {
            throw new EJBException("Cannot build an instance of the EJBContainer provider class with the name '"
                    + ejbContainerProviderName + "' in the ClassLoader '" + classLoader + "'.", e);
        }

        if (!(object instanceof EJBContainerProvider)) {
            throw new EJBException("The instance of the object with the class name '" + ejbContainerProviderName
                    + "' in the ClassLoader '" + classLoader + "' is not an instance of EJBContainer provider interface.");
        }

        // Get list
        List<EJBContainerProvider> existingEJBContainerProviders = ejbContainerProviders.get(classLoader);
        if (existingEJBContainerProviders == null) {
            existingEJBContainerProviders = new ArrayList<EJBContainerProvider>();
            ejbContainerProviders.put(classLoader, existingEJBContainerProviders);
        }

        // Add it.
        existingEJBContainerProviders.add((EJBContainerProvider) object);

    }
    
    
    
}
