/*******************************************************************************
 * Copyright (c) 1998, 2008 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 *     tware - 1.0RC1 - OSGI refactor
 *     12/23/2008-1.1M5 Michael O'Brien 
 *        - 253701: set persistenceInitializationHelper so EntityManagerSetupImpl.undeploy() can clear the JavaSECMPInitializer
 ******************************************************************************/  
package org.eclipse.persistence.jpa;

import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceUnitInfo;

import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.exceptions.PersistenceUnitLoadingException;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider;
import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl;
import org.eclipse.persistence.internal.jpa.deployment.PersistenceInitializationHelper;
import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor;
import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo;
import org.eclipse.persistence.internal.jpa.deployment.JPAInitializer;

/**
 * This is the EclipseLink EJB 3.0 provider
 * 
 * This provider should be used by JavaEE and JavaSE users.
 */
public class PersistenceProvider implements javax.persistence.spi.PersistenceProvider {

    // provides environment-specific initialization for Java EE or OSGI
    protected PersistenceInitializationHelper initializationHelper = null;    
    
    public PersistenceProvider(){
        // by default, we will work in Java EE (or SE)
        initializationHelper = new PersistenceInitializationHelper();
    }
    
    /**
     * Called by Persistence class when an EntityManagerFactory
     * is to be created.
     *
     * @param emName The name of the persistence unit
     * @param map A Map of properties for use by the
     * persistence provider. These properties may be used to
     * override the values of the corresponding elements in
     * the persistence.xml file or specify values for
     * properties not specified in the persistence.xml.
     * @return EntityManagerFactory for the persistence unit,
     * or null if the provider is not the right provider
     */
    public EntityManagerFactory createEntityManagerFactory(String emName, Map properties){
        ClassLoader classloader = initializationHelper.getClassLoader(emName, properties);
        return createEntityManagerFactory(emName, properties, classloader);
    }
    
    /**
     * Called by Persistence class when an EntityManagerFactory
     * is to be created.
     *
     * @param emName The name of the persistence unit
     * @param map A Map of properties for use by the
     * persistence provider. These properties may be used to
     * override the values of the corresponding elements in
     * the persistence.xml file or specify values for
     * properties not specified in the persistence.xml.
     * @param classLoader The classloader to search for persistence
     * units on
     * @return EntityManagerFactory for the persistence unit,
     * or null if the provider is not the right provider
     */
    protected EntityManagerFactory createEntityManagerFactory(String emName, Map properties, ClassLoader classLoader){
        Map nonNullProperties = (properties == null) ? new HashMap() : properties;
        String name = emName;
        if (name == null){
            name = "";
        }

        JPAInitializer initializer = initializationHelper.getInitializer(classLoader, nonNullProperties);
        EntityManagerSetupImpl emSetupImpl = null;

        try {
            Enumeration<URL> resources = classLoader.getResources("META-INF/persistence.xml");
            boolean initialized = false;
            while (resources.hasMoreElements()) {
                String puName = PersistenceUnitProcessor.buildPersistenceUnitName(PersistenceUnitProcessor.computePURootURL(resources.nextElement()), name);

                synchronized (EntityManagerFactoryProvider.persistenceUnits) {
                    PersistenceUnitInfo puInfo = EntityManagerFactoryProvider.getPersistenceUnitInfo(puName);
                    if (puInfo == null) {
                        if (!initialized) {
                            initializer.initialize(nonNullProperties, initializationHelper);
                            initialized = true;
                        }
                        puInfo = EntityManagerFactoryProvider.getPersistenceUnitInfo(puName);
                    }
                    if (puInfo != null) {
                        String esiName = puName;
                        if (nonNullProperties.get(PersistenceUnitProperties.SESSION_NAME) != null) {
                            esiName = puName + nonNullProperties.get(PersistenceUnitProperties.SESSION_NAME);
                        } else {
                            if (puInfo.getProperties().get(PersistenceUnitProperties.SESSION_NAME) != null) {
                                esiName = puName + puInfo.getProperties().get(PersistenceUnitProperties.SESSION_NAME);
                            }
                        }
                        emSetupImpl = EntityManagerFactoryProvider.getEntityManagerSetupImpl(esiName);
                        if (emSetupImpl == null) {
                            initializer.callPredeploy(puInfo, nonNullProperties, initializationHelper);
                            emSetupImpl = EntityManagerFactoryProvider.getEntityManagerSetupImpl(esiName);
                        }
                    }

                    // We found a match, stop looking.
                    if (emSetupImpl != null) {
                        break;
                    }
                }
            }
        } catch (Exception e) {
            throw PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(classLoader, e);
        }

        //gf bug 854  Returns null if EntityManagerSetupImpl for the name doesn't exist (e.g. a non-existent PU)
        if (emSetupImpl == null) {
            return null;
        }
            
        if (!initializer.isPersistenceProviderSupported(emSetupImpl.getPersistenceUnitInfo().getPersistenceProviderClassName())){
            return null;
        }

        // synchronized to prevent overriding of the class loader
        // and also calls to predeploy and undeploy by other threads -
        // the latter may alter result of shouldRedeploy method.
        synchronized(emSetupImpl) {
            if(emSetupImpl.shouldRedeploy()) {
                SEPersistenceUnitInfo persistenceInfo = (SEPersistenceUnitInfo)emSetupImpl.getPersistenceUnitInfo();
                persistenceInfo.setClassLoader(initializationHelper.getClassLoader(emName, properties));
                if (emSetupImpl.isUndeployed()){
                    persistenceInfo.setNewTempClassLoader(initializationHelper.getClassLoader(emName, properties));
                }
            }
            
            // 253701: cache a reference to (this) PersistenceProvider's initializer for later use during undeploy()
            emSetupImpl.setPersistenceInitializationHelper(this.initializationHelper);
            
            // call predeploy
            // this will just increment the factory count since we should already be deployed
            emSetupImpl.predeploy(emSetupImpl.getPersistenceUnitInfo(), nonNullProperties);
        }
            
        EntityManagerFactoryImpl factory = null;
        try {
            factory = new EntityManagerFactoryImpl(emSetupImpl, nonNullProperties);
        
            // This code has been added to allow validation to occur without actually calling createEntityManager
            if (emSetupImpl.shouldGetSessionOnCreateFactory(nonNullProperties)) {
                factory.getServerSession();
            }
            return factory;
        } catch (RuntimeException ex) {
            if(factory != null) {
                factory.close();
            } else {
                emSetupImpl.undeploy();
            }
            throw ex;
        }
    }

    /**
     * Called by the container when an EntityManagerFactory
     * is to be created.
     *
     * @param info Metadata for use by the persistence provider
     * @return EntityManagerFactory for the persistence unit
     * specified by the metadata
     * @param map A Map of integration-level properties for use
     * by the persistence provider.
     */
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties){
            Map nonNullProperties = (properties == null) ? new HashMap() : properties;
        
        EntityManagerSetupImpl emSetupImpl = null;
            boolean isNew = false;
            ClassTransformer transformer = null;
        synchronized (EntityManagerFactoryProvider.emSetupImpls) {
            String puName = PersistenceUnitProcessor.buildPersistenceUnitName(info.getPersistenceUnitRootUrl(), info.getPersistenceUnitName());
            String esiName = puName;
            if (nonNullProperties.get(PersistenceUnitProperties.SESSION_NAME) != null) {
                esiName = puName + nonNullProperties.get(PersistenceUnitProperties.SESSION_NAME);
            } else {
                if (info.getProperties().get(PersistenceUnitProperties.SESSION_NAME) != null) {
                    esiName = puName + info.getProperties().get(PersistenceUnitProperties.SESSION_NAME);
                }
            }
            emSetupImpl = EntityManagerFactoryProvider.getEntityManagerSetupImpl(esiName);
            if (emSetupImpl == null){
                emSetupImpl = new EntityManagerSetupImpl();
                    isNew = true;
                emSetupImpl.setIsInContainerMode(true);        
                    // if predeploy fails then emSetupImpl shouldn't be added to FactoryProvider
                    transformer = emSetupImpl.predeploy(info, nonNullProperties);
                EntityManagerFactoryProvider.addEntityManagerSetupImpl(esiName, emSetupImpl);
            }
        }
            
            if(!isNew && !emSetupImpl.isDeployed()) {
            transformer = emSetupImpl.predeploy(info, nonNullProperties);
        }
        if (transformer != null){
            info.addTransformer(transformer);
        }
        // When EntityManagerFactory is created, the session is only partially created
        // When the factory is actually accessed, the emSetupImpl will be used to complete the session construction
        EntityManagerFactoryImpl factory = new EntityManagerFactoryImpl(emSetupImpl, nonNullProperties);

        // This code has been added to allow validation to occur without actually calling createEntityManager
        if (emSetupImpl.shouldGetSessionOnCreateFactory(nonNullProperties)) {
            factory.getServerSession();
        }
        return factory;
    }

}
