/*
 * 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.unitofwork;

import java.math.BigDecimal;

import java.util.Vector;

import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.UnitOfWork;
import org.eclipse.persistence.testing.framework.AutoVerifyTestCase;
import org.eclipse.persistence.testing.framework.TestErrorException;
import org.eclipse.persistence.testing.framework.TestWarningException;


public class ConcurrentRefreshOnCloneTest extends AutoVerifyTestCase {
    // used as monitor for test
    public static ConcurrentAddress lock;

    //stores the cloned copy of the lock object
    public static ConcurrentAddress clone;

    //stores a copy of the original values
    public static ConcurrentAddress original;
    public static boolean cloneWaiting = false;
    public static boolean readerWaiting = false;

    //attribute stores if the thread has waiting to prevent the back clone from entering a wait state
    public static boolean waited = false;

    //used in remote unit of work tests to control when the lock is released
    public static int depth;
    public boolean toClone;

    //session for the threads
    public static Session session;

    //stores the original cache isolation level for this session
    int originalIsolationLevel;

    public ConcurrentRefreshOnCloneTest() {
    }

    public ConcurrentRefreshOnCloneTest(boolean toClone) {
        this.toClone = toClone;
    }

    public void setup() {
        if (getSession().isDistributedSession()) {
            throw new TestWarningException("Test unavailable on Remote UnitOfWork because of timing issues");
        }
        getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        ConcurrentRefreshOnCloneTest.session = getSession();
        ConcurrentRefreshOnCloneTest.waited = false;
        ConcurrentAddress newAddress = new ConcurrentAddress();
        newAddress.id = new BigDecimal(15);
        newAddress.city = "Meductic";
        newAddress.country = "Canada";
        newAddress.postalCode = "C1C 1C1";
        newAddress.province = "New Brunswick";
        newAddress.street = "St. Jermone";
        UnitOfWork uow = getSession().acquireUnitOfWork();
        uow.registerObject(newAddress);
        uow.commit();
        ConcurrentRefreshOnCloneTest.lock = newAddress;

        this.originalIsolationLevel = getSession().getLogin().getCacheTransactionIsolation();

        getSession().getLogin().setCacheTransactionIsolation(org.eclipse.persistence.sessions.DatasourceLogin.SYNCHRONIZED_READ_ON_WRITE);
    }

    public void test() {
        ConcurrentRefreshOnCloneTest.lock.city = "Some Large City";
        ConcurrentRefreshOnCloneTest.lock.country = "Some Small Country";
        ConcurrentRefreshOnCloneTest.lock.postalCode = "A1A 1A1";
        ConcurrentRefreshOnCloneTest.lock.province = "No Nation";
        ConcurrentRefreshOnCloneTest.lock.street = "No Name";

        ConcurrentRefreshOnCloneTest.original = (ConcurrentAddress)ConcurrentRefreshOnCloneTest.lock.clone();
        Vector primaryKey = new Vector(1);
        primaryKey.add(ConcurrentRefreshOnCloneTest.lock.getId());

        Thread thread1 = null;
        Thread thread2 = null;
        try {
            thread1 = new Thread(new ConcurrentRefreshOnCloneTest(true).runnable());
            //start the thread and wait for the thread to get under way before continueing.
            thread1.start();
            thread2 = new Thread(new ConcurrentRefreshOnCloneTest(false).runnable());
            //start the thread and wait for the thread to get under way before continueing.
            thread2.start();
        } catch (Exception ex) {
            System.out.println("Exception" + ex.toString());
            //just ignore
        }
        try {
            thread1.join();
            thread2.join();
        } catch (Exception ex) {
            //just an inturrupt ignore
        }
        if (getAbstractSession().compareObjectsDontMatch(ConcurrentRefreshOnCloneTest.lock,
                                                         ConcurrentRefreshOnCloneTest.clone)) {
            if (getAbstractSession().compareObjectsDontMatch(ConcurrentRefreshOnCloneTest.original,
                                                             ConcurrentRefreshOnCloneTest.clone)) {
                throw new TestErrorException("The test failed to clone the object without data corruption");
            }
        }
    }

    public Runnable runnable() {
        return new Runnable() {
                public void run() {
                    if (ConcurrentRefreshOnCloneTest.this.toClone) {
                        UnitOfWork uow = ConcurrentRefreshOnCloneTest.session.acquireUnitOfWork();
                        ConcurrentRefreshOnCloneTest.clone =
                                (ConcurrentAddress)uow.registerObject(ConcurrentRefreshOnCloneTest.lock);
                    } else {
                        try {
                            //sleep here to let the register catch up,
                            //can not wait here as the cloning thread may never wait if a read lock is acquired
                            Thread.sleep(5000);
                        } catch (Exception ex) {
                        }
                        ReadObjectQuery query = new ReadObjectQuery(ConcurrentAddress.class);
                        query.setShouldRefreshIdentityMapResult(true);
                        query.setSelectionCriteria(new ExpressionBuilder().get("id").equal(ConcurrentRefreshOnCloneTest.lock.getId()));
                        ConcurrentRefreshOnCloneTest.session.executeQuery(query);
                        try {
                            if (ConcurrentRefreshOnCloneTest.lock != null) {
                                synchronized (ConcurrentRefreshOnCloneTest.lock) {
                                    ConcurrentRefreshOnCloneTest.lock.notifyAll();
                                }
                            }
                        } catch (Exception ex) {
                        }
                    }
                }
            };
    }

    public void reset() {
        UnitOfWork uow = getSession().acquireUnitOfWork();
        uow.deleteObject(ConcurrentRefreshOnCloneTest.original);
        ConcurrentRefreshOnCloneTest.lock = null;
        uow.commit();
        getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        getSession().getLogin().setCacheTransactionIsolation(this.originalIsolationLevel);
    }
}
