/*******************************************************************************
 * Copyright (c) 1998, 2014 Oracle and/or its affiliates. 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
 *     11/17/2010-2.2 Guy Pelletier 
 *       - 329008: Support dynamic context creation without persistence.xml
 *     01/23/2013-2.5 Guy Pelletier 
 *       - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
 ******************************************************************************/  
package org.eclipse.persistence.testing.framework.junit;

import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import javax.persistence.*;

import junit.framework.*;

import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
import org.eclipse.persistence.logging.DefaultSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sessions.Connector;
import org.eclipse.persistence.sessions.DefaultConnector;
import org.eclipse.persistence.sessions.JNDIConnector;
import org.eclipse.persistence.sessions.broker.SessionBroker;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.testing.framework.server.JEEPlatform;
import org.eclipse.persistence.testing.framework.server.ServerPlatform;
import org.eclipse.persistence.testing.framework.server.TestRunner;
import org.eclipse.persistence.testing.framework.server.TestRunner1;
import org.eclipse.persistence.testing.framework.server.TestRunner2;
import org.eclipse.persistence.testing.framework.server.TestRunner3;
import org.eclipse.persistence.testing.framework.server.TestRunner4;
import org.eclipse.persistence.testing.framework.server.TestRunner5;

/**
 * This is the superclass for all EclipseLink JUnit tests
 * Provides convenience methods for transactional access as well as to access
 * login information and to create any sessions required for setup.
 *
 * Assumes the existence of a test.properties file on the classpath that defines the
 * following properties:
 *
 * db.platform
 * db.user
 * db.pwd
 * db.url
 * db.driver
 * 
 * If you are using the TestingBrowser, these properties come from the login panel instead.
 * If you are running the test in JEE the properties come from the server config.
 * This class should be used for all EntityManager operations to allow tests to be run in the server.
 */
public abstract class JUnitTestCase extends TestCase {

    private static Map<String, EntityManagerFactory> emfNamedPersistenceUnits = null;
    
    /** Determine if the test is running on a JEE server, or in JSE. */
    protected static Boolean isOnServer;
    
    /** Determine if the data-source is JTA, or non-JTA. */
    public static Boolean isJTA =true;
    
    /** Allow a JEE server platform to be set. */
    protected static ServerPlatform serverPlatform;
        
    /** Sets if the test should be run on the client or server. */
    public Boolean shouldRunTestOnServer;
    
    /** System variable to set the tests to run on the server. */
    public static final String RUN_ON_SERVER = "server.run";

    /** Persistence unit name associated with the test runner, null means single persistence unit */
    public String puName = null;
    
    static {
        emfNamedPersistenceUnits = new Hashtable();
    }
    
    protected static boolean isInitialzied;
    
    /** Allow OSGi specific behavior. */
    public static boolean isOSGi = false;
    
    /** Indicates whether SOP should be used */
    public static Boolean usesSOP;

    /** Indicates whether SOP should be recoverable. Ignored unless useSOP is true */
    public static Boolean isSOPRecoverable;
    
    /**
     * This is a hack to enable weaving in Spring tests.
     * The Spring agent does not load persistence units in premain
     * So it must be forced to do so before any domain classes are loaded,
     * otherwise weaving will not work.
     */
    public static void initializePlatform() {
        if (isInitialzied) {
            return;
        }
        ServerPlatform platform = getServerPlatform();
        if (platform != null) {
            platform.initialize();
        }
        isInitialzied = true;
    }
    
    public JUnitTestCase() {
        super();
        initializePlatform();
    }

    public JUnitTestCase(String name) {
        super(name);
        initializePlatform();
    }
    
    /**
     * Return the name of the persistence context this test uses.
     * This allow a subclass test to set this only in one place.
     */
    public String getPersistenceUnitName() {
        return "default";
    }
    
    /**
     * Return if the test should run on the server.
     */
    public boolean shouldRunTestOnServer() {
        if (shouldRunTestOnServer == null) {
            String property = System.getProperty(RUN_ON_SERVER);
            if (property != null) {
                shouldRunTestOnServer = property.toUpperCase().equals("TRUE");
            } else {
                shouldRunTestOnServer = false;
            }
        }
        return shouldRunTestOnServer;
    }
    
    /**
     * Return if the data-source is JTA or not.
     */
    public static boolean isJTA() {
        String property =System.getProperty("is.JTA");
        if (property != null && property.toUpperCase().equals("FALSE")) {
            isJTA = false;
        } else {
            isJTA = true;
        }
        return isJTA;
    }
    
    public boolean isWeavingForChangeTrackingEnabled() {
        return isWeavingForChangeTrackingEnabled(getPersistenceUnitName());
    }
    
    public boolean isWeavingForChangeTrackingEnabled(String persistenceUnitName) {
        Object changeTrackingWeaving = JUnitTestCase.getDatabaseSession(persistenceUnitName).getProperty("eclipselink.weaving.changetracking");
        
        if (changeTrackingWeaving == null) {
            changeTrackingWeaving = System.getProperty("eclipselink.weaving.changetracking");
        }
        
        if ("false".equals(changeTrackingWeaving)) {
            return false;
        }
        
        return true;
    }
    
    public boolean isWeavingForFetchGroupsEnabled() {
        return isWeavingForFetchGroupsEnabled(getPersistenceUnitName());
    }
    
    public boolean isWeavingForFetchGroupsEnabled(String persistenceUnitName) {
        Object fetchGroupsWeaving = JUnitTestCase.getDatabaseSession(persistenceUnitName).getProperty("eclipselink.weaving.fetchgroups");
        
        if (fetchGroupsWeaving == null) {
            fetchGroupsWeaving = System.getProperty("eclipselink.weaving.fetchgroups");
        }
        
        if ("false".equals(fetchGroupsWeaving)) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Return if the tests were run using weaving, agent or static.
     */
    public boolean isWeavingEnabled() {
        return isWeavingEnabled(getPersistenceUnitName());
    }
    
    /**
     * Return if the tests were run using weaving, agent or static.
     */
    public static boolean isWeavingEnabled(String persistenceUnitName) {
        return System.getProperty("TEST_NO_WEAVING") == null;
    }
    
    /**
     * Return if the test is running against JPA 1.0. Any test that uses 2.0
     * functionality should call this method to avoid been run against a 1.0
     * container.
     */
    public static boolean isJPA10() {
        try {
            LockModeType.valueOf("NONE");
        } catch (Exception e) {
           return true;
        }
        return false;
    }
    
    /**
     * Return if the test is running on a JEE server, or in JSE.
     */
    public static boolean isOnServer() {
        if (isOnServer == null) {
            if (System.getProperty("TEST_SERVER_PLATFORM") != null) {
                isOnServer = true;
            } else {
                isOnServer = false;                
            }
        }
        return isOnServer;
    }
    
    /**
     * Set if the test is running on a JEE server, or in JSE.
     */
    public static void setIsOnServer(boolean value) {
        isOnServer = value;
    }
    
    /**
     * Return if the Hermes parser is being used for JPQL.
     */
    public boolean isHermesParser() {
        return getDatabaseSession().getQueryBuilder().getClass().getName().indexOf("Hermes") != -1;
    }
    
    /**
     * Indicates whether SOP should be used.
     */
    public static boolean usesSOP() {
    	if (usesSOP == null) {
    		usesSOP = Boolean.valueOf(System.getProperty("sop"));
    	}
		return usesSOP;
    }
    
    /**
     * Indicates whether SOP should be recoverable. Ignored unless useSOP is true.
     */
    public static boolean isSOPRecoverable() {
    	if (isSOPRecoverable == null) {
    		isSOPRecoverable = Boolean.valueOf(System.getProperty("sop.recoverable"));
    	}
		return isSOPRecoverable;
    }
    
    /**
     * Return the server platform if running in JEE.
     */
    public static ServerPlatform getServerPlatform() {
        if (serverPlatform == null) {
            String platformClass = System.getProperty("TEST_SERVER_PLATFORM");
            if (platformClass == null) {
                serverPlatform = new JEEPlatform();
            } else {
                try {
                    serverPlatform = (ServerPlatform)Class.forName(platformClass).newInstance();
                } catch (Exception notFound) {
                    throw new RuntimeException(notFound);
                }
            }
        }
        return serverPlatform;
    }
    
    /**
     * Set the server platform, this should be done by the test executor
     * when running a test in the server.
     */
    public static void setServerPlatform(ServerPlatform value) {
        serverPlatform = value;
    }
    
    public void clearCache() {
        try {
            getDatabaseSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        } catch (Exception ex) {
            throw new RuntimeException("An exception occurred trying clear the database session cache.", ex);
        }
    }
    
    public void clearServerSessionCache() {
        try {
            getServerSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        } catch (Exception ex) {
            throw new RuntimeException("An exception occurred trying clear the server session cache.", ex);
        }
    }

    public static void clearCache(String persistenceUnitName) {
        try {
            getDatabaseSession(persistenceUnitName).getIdentityMapAccessor().initializeAllIdentityMaps();
        } catch (Exception ex) {
            throw new RuntimeException("An exception occurred trying clear the database session cache.", ex);
        }
    }

    public static void clearServerSessionCache(String persistenceUnitName) {
        try {
            getServerSession(persistenceUnitName).getIdentityMapAccessor().initializeAllIdentityMaps();
        } catch (Exception ex) {
            throw new RuntimeException("An exception occurred trying clear the server session cache.", ex);
        }
    }
        
    /**
     * Close the entity manager.
     * This allows the same code to be used on the server where managed entity managers are not closed.
     */
    public void closeEntityManager(EntityManager entityManager) {
        if (!isOnServer()) {
            entityManager.close();
        }
    }
        
    /**
     * Close the entity manager.
     * If a transaction is active, then roll it back.
     */
    public void closeEntityManagerAndTransaction(EntityManager entityManager) {
        if (isTransactionActive(entityManager)) {
            rollbackTransaction(entityManager);
        }
        closeEntityManager(entityManager);
    }
    
    /**
     * Return if the transaction is active.
     * This allows the same code to be used on the server where JTA is used.
     */
    public boolean isTransactionActive(EntityManager entityManager) {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().isTransactionActive();
        } else {
            return entityManager.getTransaction().isActive();
        }
    }

    /**
     * Return if the transaction is roll back only.
     * This allows the same code to be used on the server where JTA is used.
     */
    public boolean getRollbackOnly(EntityManager entityManager) {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().getRollbackOnly();
        } else {
            return entityManager.getTransaction().getRollbackOnly();
        }
    }
    
    /**
     * Begin a transaction on the entity manager.
     * This allows the same code to be used on the server where JTA is used,
     * and will join the EntityManager to the transaction.
     */
    public void beginTransaction(EntityManager entityManager) {
        if (isOnServer() && isJTA()) {
            getServerPlatform().beginTransaction();
            //bug 404294 - the EM is required to join the transaction to be able to 
            //    use transactions started after it was created.
            getServerPlatform().joinTransaction(entityManager);
        } else {
            entityManager.getTransaction().begin();
        }
    }

    /**
     * Commit a transaction on the entity manager.
     * This allows the same code to be used on the server where JTA is used.
     */
    public void commitTransaction(EntityManager entityManager) {
        if (isOnServer() && isJTA()) {
            getServerPlatform().commitTransaction();
        } else {
            entityManager.getTransaction().commit();
        }
    }
        
    /**
     * Rollback a transaction on the entity manager.
     * This allows the same code to be used on the server where JTA is used.
     */
    public void rollbackTransaction(EntityManager entityManager) {
        if (isOnServer() && isJTA()) {
            getServerPlatform().rollbackTransaction();
        } else {
            entityManager.getTransaction().rollback();
        }
    }
    
    /**
     * Create a new entity manager for the test suites default persistence unit.
     * If in JEE this will create or return the active managed entity manager.
     */
    public EntityManager createEntityManager() {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().getEntityManager(getPersistenceUnitName());
        } else {
            return getEntityManagerFactory(getPersistenceUnitName(), getPersistenceProperties()).createEntityManager();
        }
    }

    /**
     * Create a new entity manager for the persistence unit.
     * If in JEE this will create or return the active managed entity manager.
     */
    public static EntityManager createEntityManager(String persistenceUnitName) {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().getEntityManager(persistenceUnitName);
        } else {
            return getEntityManagerFactory(persistenceUnitName).createEntityManager();
        }
    }

    /**
     * Create a new entity manager for the persistence unit using the properties 
     * and a default persistence unit name..
     * The properties will only be used the first time this entity manager is accessed.
     * If in JEE this will create or return the active managed entity manager.
     */
    public EntityManager createEntityManager(Map properties) {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().getEntityManager(getPersistenceUnitName());
        } else {
            return getEntityManagerFactory(getPersistenceUnitName(), properties).createEntityManager(properties);
        }
    }
    
    /**
     * Create a new entity manager for the persistence unit using the properties.
     * The properties will only be used the first time this entity manager is accessed.
     * If in JEE this will create or return the active managed entity manager.
     */
    public static EntityManager createEntityManager(String persistenceUnitName, Map properties) {
        return createEntityManager(persistenceUnitName, properties, null);      
    }
    
    /**
     * Create a new entity manager for the persistence unit using the properties.
     * The properties will only be used the first time this entity manager is accessed.
     * If in JEE this will create or return the active managed entity manager.
     */
    public static EntityManager createEntityManager(String persistenceUnitName, Map properties, List<ClassDescriptor> descriptors) {
        if (isOnServer() && isJTA()) {
            return getServerPlatform().getEntityManager(persistenceUnitName);
        } else {
            return getEntityManagerFactory(persistenceUnitName, properties, descriptors).createEntityManager();
        }
    }

    public DatabaseSessionImpl getDatabaseSession() {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory().createEntityManager()).getDatabaseSession();               
    }

    public static DatabaseSessionImpl getDatabaseSession(String persistenceUnitName) {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory(persistenceUnitName).createEntityManager()).getDatabaseSession();        
    }
    
    public SessionBroker getSessionBroker() {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory().createEntityManager()).getSessionBroker();               
    }

    public static SessionBroker getSessionBroker(String persistenceUnitName) {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory(persistenceUnitName).createEntityManager()).getSessionBroker();        
    }
    
    public static ServerSession getServerSession() {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory("default").createEntityManager()).getServerSession();               
    }
    
    public static ServerSession getServerSession(String persistenceUnitName) {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory(persistenceUnitName).createEntityManager()).getServerSession();        
    }
    
    public static ServerSession getServerSession(String persistenceUnitName, Map properties) {
        return ((org.eclipse.persistence.jpa.JpaEntityManager)getEntityManagerFactory(persistenceUnitName, properties).createEntityManager()).getServerSession();        
    }
    
    public ServerSession getPersistenceUnitServerSession() {
        return getServerSession(getPersistenceUnitName());
    }
    
    public Map getPersistenceProperties() {
        return JUnitTestCaseHelper.getDatabaseProperties(getPersistenceUnitName());
    }
    
    public static EntityManagerFactory getEntityManagerFactory(String persistenceUnitName) {
        return getEntityManagerFactory(persistenceUnitName,  JUnitTestCaseHelper.getDatabaseProperties(persistenceUnitName), null);
    }
    
    public static EntityManagerFactory getEntityManagerFactory(String persistenceUnitName, Map properties) {
        return getEntityManagerFactory(persistenceUnitName, properties, null);
    }
    
    public static EntityManagerFactory getEntityManagerFactory(String persistenceUnitName, Map properties, List<ClassDescriptor> descriptors) {
        //properties.put("eclipselink.tuning", "ExaLogic");
        if (isOnServer()) {
            return getServerPlatform().getEntityManagerFactory(persistenceUnitName);
        } else {
            // Set class loader for OSGi testing.
            if (isOSGi && (properties.get(PersistenceUnitProperties.CLASSLOADER) == null)) {
                try {
                    properties.put(PersistenceUnitProperties.CLASSLOADER, JUnitTestCase.class.getClassLoader());
                } catch (Exception ignore) {
                    System.out.println(ignore);
                }
            }
            
            EntityManagerFactory emfNamedPersistenceUnit = emfNamedPersistenceUnits.get(persistenceUnitName);
            
            if (emfNamedPersistenceUnit == null) {
                
                // force closing of other persistence units to avoid Sybase running out of connections.
                if (!persistenceUnitName.equals("default")) {
                    if (emfNamedPersistenceUnits.containsKey("default") && getServerSession().getPlatform().isSybase()) {
                        Iterator<Map.Entry<String, EntityManagerFactory>> factories = emfNamedPersistenceUnits.entrySet().iterator();
                        while (factories.hasNext()) {
                            Map.Entry<String, EntityManagerFactory> entry = factories.next();
                            if (!entry.getKey().equals("default") && !entry.getKey().equals(persistenceUnitName)) {
                                System.out.println("Closing factory: " + entry.getKey());
                                entry.getValue().close();
                                factories.remove();
                            }
                        }
                    }
                }
                
                if (descriptors == null) {
                    emfNamedPersistenceUnit = Persistence.createEntityManagerFactory(persistenceUnitName, properties);
                } else {
                    emfNamedPersistenceUnit = new EntityManagerFactoryImpl(persistenceUnitName, properties, descriptors);
                }
                
                emfNamedPersistenceUnits.put(persistenceUnitName, emfNamedPersistenceUnit);

                // Force uppercase for Postgres. - no longer needed with fix for 299926: Case insensitive table / column matching 
            }
            
            return emfNamedPersistenceUnit;
        }
    }
    
    public EntityManagerFactory getEntityManagerFactory() {
        return getEntityManagerFactory(getPersistenceUnitName());
    }
    
    public EntityManagerFactory getEntityManagerFactory(Map properties) {
        return getEntityManagerFactory(getPersistenceUnitName(), properties);
    }
    
    public boolean doesEntityManagerFactoryExist() {
        return doesEntityManagerFactoryExist(getPersistenceUnitName());
    }

    public static boolean doesEntityManagerFactoryExist(String persistenceUnitName) {
        EntityManagerFactory emf = emfNamedPersistenceUnits.get(persistenceUnitName);
        return emf != null && emf.isOpen();
    }

    public void closeEntityManagerFactory() {
        closeEntityManagerFactory(getPersistenceUnitName());
    }

    public static void closeEntityManagerFactory(String persistenceUnitName) {
        EntityManagerFactory emfNamedPersistenceUnit = emfNamedPersistenceUnits.get(persistenceUnitName);
        if(emfNamedPersistenceUnit != null) {
            if(emfNamedPersistenceUnit.isOpen()) {
                emfNamedPersistenceUnit.close();
            }
            emfNamedPersistenceUnits.remove(persistenceUnitName);
        }
    }

    public Platform getPlatform() {
        return getPlatform(getPersistenceUnitName());
    }
   
    public Platform getPlatform(Class cls) {
        return getPlatform(getPersistenceUnitName(), cls);
    }
   
    public static Platform getPlatform(String puName) {
        return getDatabaseSession(puName).getPlatform();
    }
   
    public static Platform getPlatform(String puName, Class cls) {
        return getDatabaseSession(puName).getPlatform(cls);
    }
   
    public void setUp() {
    }
    
    public void tearDown() {
    }
    
    /**
     * Used to output a warning.  This does not fail the test, but provides output for someone to review.
     */
    public static void warning(String warning) {
        System.out.println("WARNING: " + warning);
    }

    /**
     * Intercept test case invocation and delegate it to a remote server.
     */
    public void runBare() throws Throwable {
        if (shouldRunTestOnServer()) {
            runBareClient();
        } else {
            super.runBare();
        }
    }

    /**
     * Runs a test by delegating method invocation to the application server.
     */
    public void runBareClient() throws Throwable {
        Properties properties = new Properties();
        String url = System.getProperty("server.url");
        if (url == null) {
            fail("System property 'server.url' must be set.");
        }
        properties.put("java.naming.provider.url", url);
        Context context = new InitialContext(properties);
        Throwable exception = null;
        if (puName == null)
        {
            String testrunner = System.getProperty("server.testrunner");
            if (testrunner == null) {
                fail("System property 'server.testrunner' must be set.");
            }
            TestRunner runner = (TestRunner) PortableRemoteObject.narrow(context.lookup(testrunner), TestRunner.class);
            exception = runner.runTest(getClass().getName(), getName(), getServerProperties());
        }else{
            int i = puName.charAt(8) - 48;
            String testRunner[] = new String[6];
            for (int j=1; j<=5; j++)
            {
                String serverRunner = "server.testrunner" + j;
                testRunner[j] = System.getProperty(serverRunner);
                if (testRunner[j] == null) {
                    fail("System property 'server.testrunner'" + j + " must be set.");
                }
            }
            switch (i)
            {
            case 1:
                TestRunner1 runner1 = (TestRunner1) PortableRemoteObject.narrow(context.lookup(testRunner[1]), TestRunner1.class);
                exception = runner1.runTest(getClass().getName(), getName(), getServerProperties());
                break;
            case 2:
                TestRunner2 runner2 = (TestRunner2) PortableRemoteObject.narrow(context.lookup(testRunner[2]), TestRunner2.class);
                exception = runner2.runTest(getClass().getName(), getName(), getServerProperties());
                break;
            case 3:
                TestRunner3 runner3 = (TestRunner3) PortableRemoteObject.narrow(context.lookup(testRunner[3]), TestRunner3.class);
                exception = runner3.runTest(getClass().getName(), getName(), getServerProperties());
                break;
            case 4:
                TestRunner4 runner4 = (TestRunner4) PortableRemoteObject.narrow(context.lookup(testRunner[4]), TestRunner4.class);
                exception = runner4.runTest(getClass().getName(), getName(), getServerProperties());
                break;
            case 5:
                TestRunner5 runner5 = (TestRunner5) PortableRemoteObject.narrow(context.lookup(testRunner[5]), TestRunner5.class);
                exception = runner5.runTest(getClass().getName(), getName(), getServerProperties());
                break;
            default:
                break;
            }
        }
        if (exception != null) {
            throw exception;
        }
    }
    
    public void runBareServer() throws Throwable {
        setIsOnServer(true);
        super.runBare();
    }
    
    /**
     * Used by subclasses to pass any properties into the
     * server's vm.  Should be used with caution.
     */
    protected Properties getServerProperties() {
        return null;
    }
    
    /**
     * Verifies that the object was merged to the cache, and written to the database correctly.
     */
    public void verifyObject(Object writtenObject) {
        verifyObject(writtenObject, getPersistenceUnitName());
    }

    /**
     * Verifies that the object was merged to the cache, and written to the database correctly.
     */
    public static void verifyObject(Object writtenObject, String persistenceUnit) {
        Object readObject = getDatabaseSession(persistenceUnit).readObject(writtenObject);
        if (!getDatabaseSession(persistenceUnit).compareObjects(readObject, writtenObject)) {
            fail("Object: " + readObject + " does not match object that was written: " + writtenObject + ". See log (on finest) for what did not match.");
        }
    }
    
    /**
     * Verifies the object in a new EntityManager.
     */
    public void verifyObjectInEntityManager(Object writtenObject) {
        verifyObjectInEntityManager(writtenObject, getPersistenceUnitName());
    }
    
    /**
     * Verifies the object in a new EntityManager.
     */
    public void verifyObjectInEntityManager(Object writtenObject, String persistenceUnit) {
        EntityManager em = createEntityManager(persistenceUnit);
        try {
            Object readObject = em.find(writtenObject.getClass(), getServerSession(persistenceUnit).getId(writtenObject));
            if (!getServerSession(persistenceUnit).compareObjects(readObject, writtenObject)) {
                fail("Object: " + readObject + " does not match object that was written: " + writtenObject + ". See log (on finest) for what did not match.");
            }
        } finally {
            closeEntityManager(em);
        }
    }
    
    /**
     * Verifies that the object was merged to the cache, and written to the database correctly.
     */
    public void verifyObjectInCacheAndDatabase(Object writtenObject) {
        verifyObjectInCacheAndDatabase(writtenObject, getPersistenceUnitName());
    }
    
    /**
     * Verifies that the object was merged to the cache, and written to the database correctly.
     */
    public static void verifyObjectInCacheAndDatabase(Object writtenObject, String persistenceUnit) {
        AbstractSession dbs = getDatabaseSession(persistenceUnit); 
        Object readObject = dbs.readObject(writtenObject);
        if (!dbs.compareObjects(readObject, writtenObject)) {
            SessionLog oldLog = dbs.getSessionLog();
            dbs.setSessionLog(new DefaultSessionLog());
            dbs.setLogLevel(SessionLog.FINEST);
            StringWriter newLog = new StringWriter();
            dbs.setLog(newLog);
            dbs.compareObjects(readObject, writtenObject);
            dbs.setSessionLog(oldLog);
            fail("Object from cache: " + readObject + " does not match object that was written: " + writtenObject + ". " + newLog);
        }
        dbs.getIdentityMapAccessor().initializeAllIdentityMaps();
        readObject = dbs.readObject(writtenObject);
        if (!dbs.compareObjects(readObject, writtenObject)) {
            SessionLog oldLog = dbs.getSessionLog();
            dbs.setSessionLog(new DefaultSessionLog());
            dbs.setLogLevel(SessionLog.FINEST);
            StringWriter newLog = new StringWriter();
            dbs.setLog(newLog);
            dbs.compareObjects(readObject, writtenObject);
            dbs.setSessionLog(oldLog);
            fail("Object from database: " + readObject + " does not match object that was written: " + writtenObject + ". " + newLog);
        }
    }

    /**
     * Compare objects.
     */
    public void compareObjects(Object obj1, Object obj2) {
        compareObjects(obj1, obj2, getPersistenceUnitName());
    }
    
    /**
     * Compare objects.
     */
    public static void compareObjects(Object obj1, Object obj2, String persistenceUnit) {
        AbstractSession dbs = getDatabaseSession(persistenceUnit); 
        if (!dbs.compareObjects(obj1, obj2)) {
            fail("Objects " + obj1 + " and " + obj2 + " are not equal. See log (on finest) for what did not match.");
        }
    }

    /**
     * Generic persist test.
     */
    public void verifyPersist(Object object) {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);
            em.persist(object);
            commitTransaction(em);
            beginTransaction(em);
            verifyObjectInCacheAndDatabase(object);
            rollbackTransaction(em);
            closeEntityManager(em);
        } catch (RuntimeException exception) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }            
            closeEntityManager(em);
            throw exception;
        }
    }
    
    /**
     * Generic remove test.
     */
    public void verifyPersistAndRemove(Object object) {
        EntityManager em = createEntityManager();
        try {
            beginTransaction(em);
            em.persist(object);
            commitTransaction(em);
            closeEntityManager(em);

            em = createEntityManager();
            beginTransaction(em);
            object = em.find(object.getClass(), getServerSession(getPersistenceUnitName()).getId(object));
            em.remove(object);
            commitTransaction(em);
            verifyDelete(object);
            closeEntityManager(em);
        }  catch (RuntimeException exception) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }            
            closeEntityManager(em);
            throw exception;
        }
    }
    
    /**
     * Verifies that the object was deleted from the database correctly.
     */
    public void verifyDelete(Object writtenObject) {
        verifyDelete(writtenObject, getPersistenceUnitName());
    }
    
    /**
     * Verifies that the object was deleted from the database correctly.
     */
    public void verifyDelete(Object writtenObject, String persistenceUnit) {
        AbstractSession dbs = getDatabaseSession(persistenceUnit);
        boolean ok;
        if (dbs.isServerSession()) {
            ok = ((ServerSession)dbs).acquireClientSession().verifyDelete(writtenObject);
        } else if (dbs.isSessionBroker()) {
            ok = ((SessionBroker)dbs).acquireClientSessionBroker().verifyDelete(writtenObject); 
        } else {
            ok = dbs.verifyDelete(writtenObject);
        }
        if (!ok) {
            fail("Object not deleted from the database correctly: " + writtenObject);
        }
    }
    
    /**
     * Allow printing off stack traces for exceptions that cause test failures when the session log level is set appropriately.  
     * Logs at at the warning level
     */
    public void logThrowable(Throwable exception){
        getDatabaseSession().getSessionLog().logThrowable(SessionLog.WARNING, exception);
    }

    /**
     * Return if pessimistic locking/select for update is supported for this test platform.
     * Currently testing supports select for update on Oracle, MySQL, SQLServer, TimesTen.
     * Some of the other platforms may have some support for select for update, but the databases we test with
     * for these do not have sufficient support to pass the tests.
     * TODO: Need to recheck tests on DB2 as it has some support for this.
     * Derby has some support, but does not work with joins (2008-12-01).
     */
    public boolean isSelectForUpateSupported(){
        return isSelectForUpateSupported(getPersistenceUnitName());
    }

    public static boolean isSelectForUpateSupported(String puName) {
        AbstractSession dbSession = getDatabaseSession(puName);
        if (dbSession.isBroker()) {
            for (AbstractSession memberSession : ((SessionBroker)dbSession).getSessionsByName().values()) {
                if (!isSelectForUpateSupported(memberSession.getPlatform())) {
                    return false;
                }
            }
            return true;
        } else {
            return isSelectForUpateSupported(dbSession.getPlatform());
        }
    }

    public static boolean isSelectForUpateSupported(DatabasePlatform platform) {
        // DB2, Derby, Symfoware (bug 304903) and Firebird support pessimistic locking only for a single-table queries.
        // PostgreSQL supports for update, but not on outerjoins, which the test uses.
        // H2 supports pessimistic locking, but has table lock issues with multiple connections used in the tests.
        if (platform.isFirebird() || platform.isH2() || platform.isHSQL() || platform.isAccess() || platform.isSQLAnywhere() || platform.isDerby() || platform.isPostgreSQL() || platform.isSymfoware()) {
            warning("This database does not support FOR UPDATE.");
            return false;
        }
        return true;
    }

    /**
     * @return true if database supports pessimistic write lock false other wise
     */
    public boolean isPessimisticWriteLockSupported() {
        return isPessimisticWriteLockSupported(getPersistenceUnitName());
    }

    public static boolean isPessimisticWriteLockSupported(String puName) {
        AbstractSession dbSession = getDatabaseSession(puName);
        if (dbSession.isBroker()) {
            for(AbstractSession memberSession : ((SessionBroker)dbSession).getSessionsByName().values()) {
                if (!isPessimisticWriteLockSupported(memberSession.getPlatform())) {
                    return false;
                }
            }
            return true;
        } else {
            return isPessimisticWriteLockSupported(dbSession.getPlatform());
        }
    }

    public static boolean isPessimisticWriteLockSupported(DatabasePlatform platform) {
        if (platform.isSybase()) { //Sybase supports getting Pessimistic Read locks but does not support getting Perssimistic Write locks
            warning("This database does not support Pessimistic Write Lock.");
            return false;
        }
        return true;
    }


    /**
     * Return if pessimistic locking/select for update nowait is supported for this test platform.
     * Currently testing supports nowait on Oracle, SQLServer.
     * PostgreSQL also supports NOWAIT, but doesn't support the outer joins used in the tests.
     */
    public static boolean isSelectForUpateNoWaitSupported(String puName) {
        AbstractSession dbSession = getDatabaseSession(puName);
        if (dbSession.isBroker()) {
            for (AbstractSession memberSession : ((SessionBroker)dbSession).getSessionsByName().values()) {
                if (!isSelectForUpateSupported(memberSession.getPlatform())) {
                    return false;
                }
            }
            return true;
        } else {
            return isSelectForUpateNoWaitSupported(dbSession.getPlatform());
        }
    }
    public boolean isSelectForUpateNoWaitSupported(){
        return isSelectForUpateNoWaitSupported(getPersistenceUnitName());
    }

    public static boolean isSelectForUpateNoWaitSupported(Platform platform) {
        if (platform.isOracle() || platform.isSQLServer()) {
            return true;
        }
        warning("This database does not support NOWAIT.");
        return false;
    }
    
    /**
     * Return if stored procedures are supported for the database platform for the test database.
     */
    public boolean supportsStoredProcedures(){
        return supportsStoredProcedures(getPersistenceUnitName());
    }

    public static boolean supportsStoredProcedures(String puName) {
        DatabasePlatform platform = getDatabaseSession(puName).getPlatform();
        // PostgreSQL has some level of support for "stored functions", but output parameters do not work as of 8.2.
        // Sybase supports stored procedures, but has issues with output parameters and transaction mode (INOUT and chaining).
        // TODO: DB2 should be in this list.
        if (platform.isOracle() || platform.isMySQL() || platform.isSQLServer()) {
            return true;
        }
        
        warning("This database does not support stored procedure creation.");
        return false;
    }

    /**
     * Return if stored functions are supported for the database platform for the test database.
     */
    public boolean supportsStoredFunctions(){
        return supportsStoredFunctions(getPersistenceUnitName());
    }

    public static boolean supportsStoredFunctions(String puName) {
        DatabasePlatform platform = getDatabaseSession(puName).getPlatform();
        // PostgreSQL has some level of support for "stored functions", but output parameters do not work as of 8.2.
        // TODO: DB2 should be in this list.
        if (platform.isOracle() || platform.isMySQL()) {
            return true;
        }
        warning("This database does not support stored function creation.");
        return false;
    }

    public void setPuName(String name){
        puName = name;
    }
    
    /**
     * Indicates whether two sessions are connected to the same db
     */
    public static boolean usingTheSameDatabase(AbstractSession session1, AbstractSession session2) {
        Connector conn1 = session1.getLogin().getConnector();
        Connector conn2 = session2.getLogin().getConnector();
        if (conn1 instanceof DefaultConnector && conn2 instanceof DefaultConnector) {
            return ((DefaultConnector)conn1).getDatabaseURL().equals(((DefaultConnector)conn2).getDatabaseURL());
        } else if (conn1 instanceof JNDIConnector && conn2 instanceof JNDIConnector) {
            String name1 = ((JNDIConnector)conn1).getName();
            String name2 = ((JNDIConnector)conn2).getName();
            if (name1 != null && name1.equals(name2)) {
                return true;
            }
            return ((JNDIConnector)conn1).getDataSource().equals(((JNDIConnector)conn2).getDataSource());
        }
        return false;
    }
}
