/*
 * Copyright (c) 2011, 2020 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:
//     tware - test for bug 324459
package org.eclipse.persistence.testing.tests.simultaneous;

import java.math.BigDecimal;

import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.queries.ReadObjectQuery;
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.models.employee.domain.PhoneNumber;
import org.eclipse.persistence.testing.tests.unitofwork.ConcurrentPerson;
import org.eclipse.persistence.testing.tests.unitofwork.ConcurrentPhoneNumber;

/**
 *  Test for bug 324459
 *
 *  This test test a bidirectional relationship where one side is EAGER and the other is LAZY
 *  The object with the EAGER relationship does a simple ReadObjectQuery
 *  The object with the LAZY relationahip does a ReadObjectQuery with a fetch join that
 *  traverses the relationship.
 *  This test tests to ensure there is no deadlock in that situation
 * @author tware
 *
 */
public class ConcurrentReadFetchJoinTest extends AutoVerifyTestCase{

    public static final String NUMBER_TYPE = "cell";

    protected BigDecimal personId = null;
    protected BigDecimal projectId = null;
    protected ConcurrentPerson person = null;

    protected boolean deadlockDetected = false;

    public void setup(){
        deadlockDetected = false;
        UnitOfWork uow = getSession().acquireUnitOfWork();
        person = new ConcurrentPerson();
        person.name = "Wenger";
        ConcurrentPhoneNumber phoneNumber = new ConcurrentPhoneNumber();
        phoneNumber.setType(NUMBER_TYPE);
        phoneNumber.setNumber("1234567");
        phoneNumber.setOwner(person);
        person.phoneNumbers.add(phoneNumber);
        uow.registerObject(person);
        uow.registerObject(phoneNumber);

        uow.commit();
        uow.release();
        getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        personId = person.id;
    }

    public void test(){
        ConcurrentPerson.RUNNING_TEST = ConcurrentPerson.READ_FETCH_JOIN;
        Thread thread1 = new Thread(new PersonReader(getSession().acquireUnitOfWork(), personId));

        Thread thread2 = new Thread(new PhoneNumberReader(getSession().acquireUnitOfWork(), personId, NUMBER_TYPE));
        thread1.start();
        thread2.start();
        try {
            thread1.join(30000);
            if (thread1.isAlive()){
                try{
                    thread1.interrupt();
                    thread2.interrupt();
                }catch (Exception e) {
                }
                deadlockDetected = true;
            }
        } catch (InterruptedException ex) {

        }
    }

    public void verify(){
        if (deadlockDetected){
            throw new TestErrorException("Deadlock detected when reading a bidirectional relationship with a fetch join.");
        }
    }

    public void reset(){
        ConcurrentPerson.RUNNING_TEST = ConcurrentPerson.NONE;
        UnitOfWork uow = getSession().acquireUnitOfWork();
        uow.deleteObject(person.getPhoneNumbers().get(0));
        uow.deleteObject(person);
        uow.commit();
        getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
    }

    public class PersonReader implements Runnable{
        private UnitOfWork uow;
        private BigDecimal id;

        public PersonReader(UnitOfWork uow, BigDecimal id){
            this.uow = uow;
            this.id = id;
        }

        public void run(){
            uow.readObject(ConcurrentPerson.class, (new ExpressionBuilder()).get("id").equal(id));
        }
    }

    public class PhoneNumberReader implements Runnable{
        private UnitOfWork uow;
        private BigDecimal id;
        private String type;

        public PhoneNumberReader(UnitOfWork uow, BigDecimal id, String type){
            this.uow = uow;
            this.id = id;
            this.type = type;
        }

        public void run(){
            ReadObjectQuery query = new ReadObjectQuery(ConcurrentPhoneNumber.class);
            ExpressionBuilder phoneNumberBuilder = query.getExpressionBuilder();
            Expression exp = phoneNumberBuilder.get("type").equal(type).and(phoneNumberBuilder.get("owner").get("id").equal(id));
            query.setSelectionCriteria(exp);
            query.addJoinedAttribute("owner");
            uow.executeQuery(query);
        }
    }
}
