/*
 * Copyright (c) 1998, 2021 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.identitymaps.cache;

import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.testing.tests.clientserver.Server;
import org.eclipse.persistence.testing.models.bigbad.*;


/**
 * This test is set up to have one thread build a BigBadObject instance, while multiple other threads
 * attempt to access the same instance from the cache.  The test fails if any of the threads access
 * an incomplete instance of the BigBadObject object - where it has been only partially built.  Only
 * the "number02" attribute is checked, as all mapping weights are changed so this mapping is
 * built last.  Built to test bug 4772232
 */
public class ConcurrentReadBigBadObjectTest extends TestCase {
    public ConcurrentReadBigBadObjectTest() {
    }

    BigBadObject referenceObject;
    protected int numOftries = 300;
    protected int numOfThreads= 10;
    protected boolean failed;
    protected Server server;
    protected int OrigDirectMapWeight;


    @Override
    public void setup() throws Exception{

        DatabaseLogin login;
        login = (DatabaseLogin) getSession().getLogin().clone();
        server = new Server(login);
        server.serverSession.setLogLevel(getSession().getLogLevel());
        server.serverSession.setLog(getSession().getLog());


        server.copyDescriptors(getSession());
        ClassDescriptor d = (server.serverSession).getClassDescriptor(BigBadObject.class);
        DatabaseMapping m;

        Vector<DatabaseMapping> v = d.getMappings();
        int mappings = v.size();
        int i =0;
        while (i<mappings){
            m = v.get(i);
            m.setWeight(Integer.MAX_VALUE - 1);
            i++;
        }

        m = d.getMappingForAttributeName("number02");
        m.setWeight(Integer.MAX_VALUE);

        server.login();
        server.serverSession.setLogLevel(getSession().getLogLevel());
        server.serverSession.setLog(getSession().getLog());
    }

    @Override
    public void reset() throws Exception{
        server.logout();
    }

    @Override
    public void test() {
        failed=false;

        referenceObject = (BigBadObject)this.getSession().readObject(BigBadObject.class);
        Reader[] threadList = new Reader[numOfThreads];

        for (int i=0;i<numOfThreads;){
            threadList[i]=new Reader(referenceObject, server.serverSession.acquireClientSession(), ++i);
        }

        for (int i=0;i<numOfThreads;i++){
             threadList[i].start();
        }

        try{
            for (int i=0;i<numOfThreads;i++){
                threadList[i].join();
            }
        }catch (InterruptedException ex){
        }
        int count=0;
        while(!failed && count<numOfThreads){
            if(threadList[count].exception!=null){
                throw threadList[count].exception;
            }
            failed = threadList[count].hadError();
            count++;
        }
        if (failed){
            throw new TestErrorException("Test failed, getFromIdentityMap returned an object before it was finished being built");
        }

    }


    /*
     * Threads to read/access the cache numOftries times.  Only thread 1 will initialize the cache and
     * read the BigBadObject object instance.
     */
    private class Reader extends Thread {
        protected BigBadObject referenceObject, readObject;
        protected Session session;
        public int thread;
        public int counter;
        public RuntimeException exception;

        protected boolean experienceError = false;

        public Reader(BigBadObject object, Session session, int thread){
            this.referenceObject = object;
            this.session = session;
            this.thread= thread;
            counter=0;
        }

        @Override
        public void run(){
            try{
                counter=0;
                while(!experienceError && counter<numOftries){
                    if( thread==1){
                        session.getIdentityMapAccessor().initializeIdentityMap(BigBadObject.class);
                        readObject = (BigBadObject)session.readObject(referenceObject);
                    }else{
                        readObject = (BigBadObject)session.getIdentityMapAccessor().getFromIdentityMap(referenceObject);
                    }
                    if ((readObject!=null)&&
                        ( (readObject.number02==null) || (!readObject.number02.equals(referenceObject.number02)) ) ){
                        this.experienceError = true;
                    }
                    counter++;
                }
            }catch (RuntimeException ex){
                this.experienceError = true;
                this.exception=ex;
            }
        }

        public boolean hadError(){
            return experienceError;
        }
    }
}
