/*
 * Copyright (c) 1998, 2018 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 v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.jpa.fieldaccess.advanced;

import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.fieldaccess.advanced.*;
import org.eclipse.persistence.exceptions.OptimisticLockException;

import junit.framework.Test;
import junit.framework.TestSuite;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.PersistenceException;

/**
 * <p>
 * <b>Purpose</b>: Test TopLink's EJB 3.0 optimistic concurrency functionality.
 * <p>
 * <b>Description</b>: This class creates a test suite and adds tests to the
 * suite. The database gets initialized prior to the test methods.
 * <p>
 * <b>Responsibilities</b>:
 * <ul>
 * <li> Run tests for TopLink's EJB 3.0 optimistic concurrency functionality.
 * </ul>
 * @see org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator
 */
 public class OptimisticConcurrencyJUnitTestSuite extends JUnitTestCase {

    public OptimisticConcurrencyJUnitTestSuite() {
        super();
    }

    public OptimisticConcurrencyJUnitTestSuite(String name) {
        super(name);
    }

    public static Test suite() {
        TestSuite suite = new TestSuite("Optimistic Concurrency (fieldaccess)");
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testSetup"));
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testCreateProjects"));
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testCreateEmployeeWithFlush"));
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testVersionUpdateWithCorrectValue"));
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testVersionUpdateWithIncorrectValue"));
        suite.addTest(new OptimisticConcurrencyJUnitTestSuite("testVersionUpdateWithNullValue"));

        return suite;
    }

    /**
     * The setup is done as a test, both to record its failure, and to allow execution in the server.
     */
    public void testSetup() {
        new AdvancedTableCreator().replaceTables(JUnitTestCase.getServerSession("fieldaccess"));
        clearCache("fieldaccess");
    }

    /**
     * Creates two projects used in later tests.
     */
    public void testCreateProjects() {
        EntityManager em = createEntityManager("fieldaccess");
        Project project1, project2;

        beginTransaction(em);
        project1 = ModelExamples.projectExample1();
        project2 = ModelExamples.projectExample2();
        em.persist(project1);
        em.persist(project2);
        commitTransaction(em);
    }

    /**
     * test for issue 635: NullPointerException occuring for Object typed version fields.
     * Employee has a write lock (version) field of type Integer
     * The NullPointerException is thrown when comparing versions in
     * ObjectChangeSet#compareWriteLockValues
     */
    public void testCreateEmployeeWithFlush() {
        EntityManager em = createEntityManager("fieldaccess");
        Project project1, project2;
        Employee employee;

        try {
            beginTransaction(em);
            employee = ModelExamples.employeeExample1();
            em.persist(employee);

            // first flush: Employee is written to the database
            Query query = em.createNamedQuery("findFieldAccessProjectByName");
            query.setParameter("name", "Farmer effecency evaluations");
            project1 = (Project) query.getSingleResult();
            employee.getProjects().add(project1);

            // second flush: Employee is modified, but
            // no update to EMPLOYEE table; only join table entry is written
            query = em.createNamedQuery("findFieldAccessProjectByName");
            query.setParameter("name", "Feline Demographics Assesment");
            project2 = (Project) query.getSingleResult();
            employee.getProjects().add(project2);

            // third flush: Employee is modified, but
            // no update to EMPLOYEE table; only join table entry is written
            // A NullPointerException in ObjectChangeSet#compareWriteLockValues
            commitTransaction(em);
        } catch (RuntimeException e) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
            throw e;
        }
    }

    /**
     * test: updating the version field with the in-memory value.
     * This should be allowed; there's no change for TopLink to detect this.
     */
    public void testVersionUpdateWithCorrectValue() {
        EntityManager em = createEntityManager("fieldaccess");
        Employee employee;

        try {
            beginTransaction(em);
            employee = ModelExamples.employeeExample1();
            em.persist(employee);
            commitTransaction(em);

            beginTransaction(em);
            employee.setVersion(1);
            commitTransaction(em);
        } catch (RuntimeException re) {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
            throw re;
        }
    }

    /**
     * test: updating the version field with value != in-memory value.
     * This should throw an OptimisticLockException
     */
    public void testVersionUpdateWithIncorrectValue() {
        EntityManager em = createEntityManager("fieldaccess");
        Employee employee;

        try {
            beginTransaction(em);
            employee = ModelExamples.employeeExample1();
            em.persist(employee);
            commitTransaction(em);

            beginTransaction(em);
            Employee employee1 = em.find(Employee.class, employee.getId());
            employee1.setVersion(2);
            commitTransaction(em);
            fail("updating object version with wrong value didn't throw exception");
        } catch (PersistenceException pe) {
            // expected behavior
        } catch (Exception exception) {
            Throwable persistenceException = exception;
            // Remove an wrapping exceptions such as rollback, runtime, etc.
            while (persistenceException != null && !(persistenceException instanceof OptimisticLockException)) {
                // In the server this is always a rollback exception, need to get nested exception.
                persistenceException = persistenceException.getCause();
            }
            if (persistenceException instanceof OptimisticLockException) {
                return;
            } else {
                fail("updating object version with wrong value threw a wrong exception: " + exception.getMessage());
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }

    /**
     * test: updating the version field with null value.
     * This should throw an exception
     */
    public void testVersionUpdateWithNullValue() {
        EntityManager em = createEntityManager("fieldaccess");
        Employee employee;

        try {
            beginTransaction(em);
            employee = ModelExamples.employeeExample1();
            em.persist(employee);
            commitTransaction(em);

            beginTransaction(em);
            Employee employee2 = em.find(Employee.class, employee.getId());
            employee2.setVersion(null);
            commitTransaction(em);
            fail("employee2.setVersion(null) didn't throw exception");
        } catch (PersistenceException pe) {
            // expected behavior
        } catch (Exception exception) {
            Throwable persistenceException = exception;
            // Remove an wrapping exceptions such as rollback, runtime, etc.
            while (persistenceException != null && !(persistenceException instanceof OptimisticLockException)) {
                // In the server this is always a rollback exception, need to get nested exception.
                persistenceException = persistenceException.getCause();
            }
            if (persistenceException instanceof OptimisticLockException) {
                return;
            } else {
                fail("employee2.setVersion(null) threw a wrong exception: " + exception.getMessage());
            }
        } finally {
            if (isTransactionActive(em)){
                rollbackTransaction(em);
            }
            closeEntityManager(em);
        }
    }
}
