Package com.force.sdk.jpa

Source Code of com.force.sdk.jpa.OptimisticTxTestMT$ScriptRunner

/**
* Copyright (c) 2011, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
*    Redistributions of source code must retain the above copyright notice, this list of conditions and the
*    following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*    the following disclaimer in the documentation and/or other materials provided with the distribution.
*
*    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
*    promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

package com.force.sdk.jpa;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.persistence.*;

import org.testng.Assert;
import org.testng.annotations.*;

import com.force.sdk.connector.ForceConnectorConfig;
import com.force.sdk.connector.ForceServiceConnector;
import com.force.sdk.jpa.entities.*;
import com.force.sdk.jpa.entities.TestEntity.PickValues;
import com.force.sdk.qa.util.TestContext;
import com.force.sdk.qa.util.UserInfo;
import com.force.sdk.qa.util.jpa.BaseJPAFTest;
import com.sforce.soap.partner.Connector;
import com.sforce.ws.ConnectionException;

/**
* Tests optimistic transaction support in JPA. Tests use 2 threads to create concurrency
* and simulate test scenarios.
*
* @author Dirk Hain
*/
public class OptimisticTxTestMT extends BaseJPAFTest {

    private static final OptimisticLockException OLE =
            new OptimisticLockException("Some instances failed to flush successfully due to optimistic verification problems.");
    protected int threadcount = 0;
   
    EntityManager em3;
    EntityManagerFactory emfac2;
    EntityManagerFactory emfac3;
   
    @Override
    @BeforeClass(enabled = false)
    public void initialize() throws IOException, ConnectionException {
        super.initialize();
        em3 = getAdditionalEntityManagers().get(TestContext.get().getPersistenceUnitName() + "3");
        emfac2 = TestContext.get().getEntityManagerFactoryMap().get(TestContext.get().getPersistenceUnitName() + "2");
        emfac3 = TestContext.get().getEntityManagerFactoryMap().get(TestContext.get().getPersistenceUnitName() + "3");
    }
   
    @Override
    public Set<String> getAdditionalPersistenceUnitNames() {
        return new HashSet<String>(Arrays.asList(new String[] {
                TestContext.get().getPersistenceUnitName() + "2",
                TestContext.get().getPersistenceUnitName() + "3"
        }));
    }

    /**
     * Helper enum to model the execution protocol via an array.
     *
     * @author Dirk Hain
     *
     */
    public enum Op {
        SWITCHCONTROL(0),
        READ(1),
        UPDATE(2),
        DELETE(3),
        WAIT(4),
        UPDATEwException(5),
        DELETEwException(6),
        COMMIT(7),
        ROLLBACK(8),
        UpdEmb(9),
        UpdRel(11),
        UpdPckl(12),
        UpdTransient(13),
        UpdRelField(14),
        UpdEmbField(15);
        int op;

        private Op(int o) {
            op = o;
        }
    }

    @BeforeClass(enabled = false, dependsOnMethods = "initialize")
    protected void init() {
        deleteAll(ParentTestEntity.class);
        deleteAll(TestEntity.class);
        deleteAll("Case");
        deleteAll("Account");
    }


    @AfterClass(enabled = false)
    protected void classTearDown() {
        deleteAll(ParentTestEntity.class);
        deleteAll(TestEntity.class);
        deleteAll("Case");
        deleteAll("Account");
    }

    @DataProvider
    public Object[][] txData() throws NumberFormatException, MalformedURLException {
        Object[][] scenarios = new Object[][]{
                {"concurrent update", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.READ},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent delete", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.DELETE, Op.COMMIT}},
                        null}, //deleting a modified entity is allowed
                {"concurrent delete a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.DELETE, Op.COMMIT}},
                        null}, //deleting a modified entity is allowed
                {"concurrent update w/ rollback", emfac2,
                        new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.SWITCHCONTROL, Op.WAIT, Op.ROLLBACK, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.SWITCHCONTROL, Op.WAIT, Op.COMMIT}},
                        null},
                {"concurrent upd w/ rollback a-o-n", emfac3,
                        new Op[][]{{Op.READ, Op.WAIT, Op.UPDATE, Op.SWITCHCONTROL, Op.WAIT, Op.ROLLBACK, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.SWITCHCONTROL, Op.WAIT, Op.COMMIT}},
                        null},
                {"concurrent picklist update", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdPckl, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent picklist update a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UpdPckl, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update transient field", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdTransient, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.SWITCHCONTROL}},
                        null}, //transient update is allowed
                {"concurrent update transient field a-o-n", emfac3,
                        new Op[][]{{Op.READ, Op.WAIT, Op.UpdTransient, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.SWITCHCONTROL}},
                        null}, //transient update is allowed
                {"concurrent update relationship", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdRel, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.SWITCHCONTROL}},
                        null}, //related entity can change
                {"concurrent update relationship a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UpdRel, Op.COMMIT, Op.WAIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATE, Op.COMMIT, Op.SWITCHCONTROL}},
                        null}, //related entity can change
                {"concurrent update relationship field", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdRelField, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update relationship field a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UpdRelField, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update embedded field", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdEmbField, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update embedded field a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UpdEmbField, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update embedded entity", emfac2, new Op[][]{{Op.READ, Op.WAIT, Op.UpdEmb, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
                {"concurrent update embedded entity a-o-n", emfac3, new Op[][]{{Op.READ, Op.WAIT, Op.UpdEmb, Op.COMMIT},
                        {Op.WAIT, Op.READ, Op.WAIT, Op.UPDATEwException}},
                        OLE},
        };
        return scenarios;
    }


    @Test(enabled = false, dataProvider = "txData", invocationCount = 1, timeOut = 15000)
    /**
     * Optimistic transaction tests for multiple transactional scenarios.
     * Test multiple scenarios like dirty read or lost update to verify optimistic transaction support of the javasdk.
     * @hierarchy javasdk
     * @userStory xyz
     * @expectedResults All operations in array prot are executed successfully.
     */
    public <E extends Exception> void runTxScenariosTest(String scenarioName, EntityManagerFactory emf, Op[][] prot, E ex)
    throws InterruptedException, ConnectionException, IOException {

        //test data
        AnnotatedEntity ane = new TestEntity();
        JPATestUtils.initializeTestEntity(ane);
        ParentTestEntity pane = JPATestUtils.setMasterDetailRelationship(ane);
        EntityTransaction tx = em.getTransaction();
        if (!tx.isActive()) tx.begin();
        em.persist(pane);
        em.persist(ane);
        tx.commit();
        Thread.sleep(1000); //this is required as otherwise the first update might happen within the same second

        BlockingQueue<Long> semaphor = new ArrayBlockingQueue<Long>(3) {  };
        LinkedList<Throwable> excQ = new LinkedList<Throwable>();
        StringBuffer errLog = new StringBuffer("-----------------------------\nSCENARIO " + scenarioName + " START\n");

        for (int i = 0; i < prot.length; i++) {
            TestContext t = TestContext.get();
            new Thread(new ScriptRunner<E>(semaphor, excQ, errLog, t.getUserInfo(),
                    emf, ex, ane.getId(), ane.getClass(), pane.getId(), Arrays.asList(prot[i]))).start();
        }
        synchronized (excQ) {
            try {
                while (threadcount > 0) {
                    excQ.wait();
                    errLog.append("SCENARIO " + scenarioName + " END\n-----------------------------\n");
                }
            } catch (InterruptedException e1) {
                errLog.append("***SCENARIO " + scenarioName + " was interrupted.");
                excQ.add(e1);
            } finally {
                if (!excQ.isEmpty()) {
                    Iterator<Throwable> it = excQ.iterator();
                    while (it.hasNext()) {
                        Throwable error = it.next();
                        errLog.append("\n" + error.getCause());
                        errLog.append("\n" + error.getMessage());
                    }
                    Assert.fail("***FAILED SCENARIO: " + scenarioName + "\n" + errLog);
                }
               
                // Ensure the ForceServiceConnector ThreadLocal is cleaned up
                ForceServiceConnector.setThreadLocalConnectorConfig(null);
            }
        }
    }


    /**
     * Typical producer for producer-consumer pattern. Synchronization through {@link BlockingQueue}.
     * @author Dirk Hain
     *
     */
    @SuppressWarnings("rawtypes")
    class ScriptRunner<E extends Exception> implements Runnable {

        protected BlockingQueue<Long> sema; //thread synchronization
        protected LinkedList<Throwable> exceptions; //test synchronization
        protected StringBuffer errLog; //shared StringBuffer between threads
        private EntityTransaction tx;
        private Stack<Op> protocol; //thread's protocol to run
        private EntityManager tem; //EM to use
        private String entityId; //entity to work on
        private String parentId; //parent to work on
        private Class clazz;
        private Exception exType; //expected exception, null if none expected

        ScriptRunner(BlockingQueue<Long> q, LinkedList<Throwable> m, StringBuffer err, UserInfo ctx,
                EntityManagerFactory emf, E exc, String id, Class c, String parId, List<Op> p)
                throws ConnectionException, IOException {
            this.sema = q;
            this.exceptions = m;
            this.errLog = err;
            this.entityId = id;
            this.parentId = parId;
            this.clazz = c;
            this.exType = exc;
            protocol = new Stack<Op>();
            Collections.reverse(p); //reverse order since we use a stack during execution
            protocol.addAll(p);
            threadcount++;
            loadThreadContext(ctx);
            this.tem = emf.createEntityManager();
            this.tx = tem.getTransaction();
        }

        /**
         * Initializes the thread to access the dynamically created test org.
         */
        private void loadThreadContext(UserInfo info) throws ConnectionException, IOException {
            Assert.assertNotNull(info, "UserInfo is not specified.");
            Assert.assertNotNull(info.getUserName(), "UserName is not specified.");
            ForceConnectorConfig cc = new ForceConnectorConfig();
            cc.setUsername(info.getUserName());
            cc.setPassword(info.getPassword());
            cc.setAuthEndpoint(info.getServerEndpoint());
            service = Connector.newConnection(cc);
            ForceServiceConnector.setThreadLocalConnectorConfig(cc); //required so that this thread uses dynamic org
        }

        @Override
        @SuppressWarnings("unchecked")
        public void run() {
            AnnotatedEntity testRef = (AnnotatedEntity) tem.find(clazz, entityId); //obtain a local copy to create concurrency
            ParentTestEntity parRef = tem.find(ParentTestEntity.class, parentId);
            try {
                while (true) {
                    if (protocol.empty()) return; //work for this thread is done
                    Op o = protocol.pop();
                    switch(o) {
                        case COMMIT:
                            String before = "";
                            Calendar commitedTime = testRef.getLastModifiedDate();
                            if (tx.isActive()) {
                                before = testRef.getId(); //Will lose Id if delete is committed.
                                tx.commit();
                                errLog.append(getThreadPrefix() + "Committed. "
                                                + "lMod before: " + getPrintDateMs(commitedTime) + " Id: " + before + "\n");
                            }
                            sema.put(Thread.currentThread().getId());
                            break;

                        case ROLLBACK:
                            if (tx.isActive()) {
                                tx.rollback();
                                errLog.append(getThreadPrefix() + "Rollback at " + getPrintDateMs(Calendar.getInstance()) + "\n");
                            }
                            sema.put(Thread.currentThread().getId());
                            break;

                        case READ:
                            testRef = tem.find(testRef.getClass(), entityId);
                            errLog.append(getThreadPrefix() + "Read :" + entityId
                                            + " lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            sema.put(Thread.currentThread().getId());
                            break;

                        case UPDATE:
                            if (!tx.isActive()) tx.begin();
                            String beforeName = testRef.getName();
                            before = testRef.getId();
                            testRef.setName("Update_" + Long.toString(System.currentTimeMillis()));
                            tem.merge(testRef);
                            errLog.append(getThreadPrefix() + "Modified at " + getPrintDateMs(testRef.getLastModifiedDate())
                                            + ": " + before + ": " + beforeName + " to " + testRef.getName() + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case DELETE:
                            if (!tx.isActive()) tx.begin();
                            tem.merge(testRef);
                            tem.remove(testRef);
                            errLog.append(getThreadPrefix() + "Deleted: " + testRef.getId() + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case UPDATEwException:
                            try {
                                if (!tx.isActive()) tx.begin();
                                beforeName = testRef.getName();
                                before = testRef.getId();
                                testRef.setName("Update_" + Long.toString(System.currentTimeMillis()));
                                tem.merge(testRef);
                                errLog.append(getThreadPrefix() + "Attempted modification at "
                                                + getPrintDateMs(testRef.getLastModifiedDate()) + ": " + before
                                                + ": " + beforeName + " to " + testRef.getName() + "\n");
                                tx.commit();
                                Thread.sleep(1000); //scheduler swap after 1s
                                Assert.fail("Expected exception during update of " + entityId);
                            } catch (Exception ue) {
                                Assert.assertTrue(exType.getClass().isInstance(ue.getCause()),
                                        "Wrong exception type received: " + ue.getMessage());
                                Assert.assertTrue(ue.getCause().getMessage().contains(exType.getMessage()),
                                        "Wrong exception message on update. " + ue.getMessage());
                            }
                            break;

                        case DELETEwException:
                            try {
                                if (!tx.isActive()) tx.begin();
                                before = testRef.getId();
                                tem.merge(testRef);
                                tem.remove(testRef);
                                errLog.append(getThreadPrefix() + "Attempted deletion. "
                                              + "lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + ": " + before + "\n");
                                tx.commit();
                                Assert.fail("Expected exception during delete of " + entityId);
                            } catch (Exception de) {
                                Assert.assertTrue(exType.getClass().isInstance(de),
                                        "Wrong exception type received: " + de.getMessage());
                                Assert.assertTrue(de.getMessage().contains(exType.getMessage()),
                                        "Wrong exception message on delete. " + de.getMessage());
                            }
                            break;

                        case UpdEmb:
                            if (!tx.isActive()) tx.begin();
                            EmbeddedTestEntity ete = testRef.getEmbedded();
                            ete.setEmbedded("" + System.currentTimeMillis());
                            tem.merge(testRef);
                            errLog.append(getThreadPrefix() + "Modified embedded entity. "
                                            + "lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            Thread.sleep(1000);
                            break;

                        case UpdEmbField:
                            if (!tx.isActive()) tx.begin();
                            before = testRef.getId();
                            ete = new EmbeddedTestEntity();
                            ete.setEmbedded("Upd_" + System.currentTimeMillis());
                            testRef.setEmbedded(ete);
                            tem.merge(testRef);
                            errLog.append(getThreadPrefix() + "Modified embedded field. "
                                            + "lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case UpdRel:
                            if (!tx.isActive()) tx.begin();
                            parRef.setName("new Parent name" + System.currentTimeMillis());
                            tem.merge(parRef);
                            errLog.append(getThreadPrefix() + "Modified parent. TestEntity "
                                            + "lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case UpdRelField:
                            if (!tx.isActive()) tx.begin();
                            Thread.sleep(1000); //scheduler swap after 1s
                            parRef.setName("new Parent name " + System.currentTimeMillis());
                            tem.merge(parRef);
                            testRef.setParent(parRef);
                            errLog.append(getThreadPrefix() + "Modified parent and parent ref. "
                                            + "TestEntity lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case UpdPckl:
                            if (!tx.isActive()) tx.begin();
                            testRef.setPickValueMulti(new PickValues[] {PickValues.THREE});
                            tem.merge(testRef);
                            errLog.append(getThreadPrefix() + "Modified picklist value. "
                                            + "lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            Thread.sleep(1000); //scheduler swap after 1s
                            break;

                        case UpdTransient:
                            if (!tx.isActive()) tx.begin();
                            testRef.setUnused("Upd_" + System.currentTimeMillis());
                            tem.merge(testRef);
                            errLog.append(getThreadPrefix() + "Modified transient. "
                                            + "TestEntity lMod: " + getPrintDateMs(testRef.getLastModifiedDate()) + "\n");
                            break;

                        case SWITCHCONTROL:
                            sema.put(Thread.currentThread().getId());
                            break;

                        case WAIT:
                            long token = sema.take();
                            errLog.append(getThreadPrefix() + "Waited." + "\n");
                            if (token == Thread.currentThread().getId()) {
                                sema.put(token);
                                Thread.sleep(100);
                                this.protocol.push(o); //wait some more
                            }
                            break;
                         default:
                             Assert.fail("Unknown Op: " + o);
                    }
                }
            } catch (Throwable notExpected) {
                exceptions.add(notExpected);
                while (notExpected.getCause() != null) {
                    exceptions.add(notExpected.getCause());
                    notExpected = notExpected.getCause();
                }
            } finally {
                try {
                    sema.put(Thread.currentThread().getId()); //release any waiting threads
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                threadcount--;
                errLog.append(getThreadPrefix() + "End thread on " + getPrintDateMs(Calendar.getInstance()) + "\n");
                if (threadcount == 0) {
                    synchronized (exceptions) {
                        exceptions.notify();
                    }
                }
            }
        }
    }


    @Test(enabled = false)
    /**
     * Tests for attribute with @Version.
     * Verifies that entities get versioned properly and that the attribute defined with @Version
     * (typically {@link TestEntity.lastModifiedDate}) is updated upon change.
     * @hierarchy javasdk
     * @userStory xyz
     * @expectedResults Version property {@link TestEntity.lastModifiedDate} is assigned a new value.
     */
    public void testEntityVersioning() throws InterruptedException {
        AnnotatedEntity a = new TestEntity();
        JPATestUtils.initializeTestEntity(a);
        ParentTestEntity p = JPATestUtils.setMasterDetailRelationship(a);
        EntityTransaction tx = em.getTransaction();
        if (!tx.isActive()) tx.begin();
        em.persist(p);
        em.persist(a);
        tx.commit();
        Thread.sleep(1000L);
        a = em.find(a.getClass(), a.getId());
        Calendar before = a.getLastModifiedDate();
        Assert.assertTrue(before != null, "@Version property was not created.");
        EntityTransaction t = em.getTransaction();
        if (!t.isActive()) t.begin();
        a.setStringObject("String_" + System.currentTimeMillis());
        em.merge(a);
        t.commit();
        a = em.find(a.getClass(), a.getId());
        Calendar after = a.getLastModifiedDate();
        Assert.assertTrue(after.after(before), "@Version property is not dated later than before the change.");
    }
   
    @Test(enabled = false)
    /**
     * Update entity without @Version attribute.
     * Test that updating entities, one with @Version and one without, will still succeed.
     * @hierarchy javasdk
     * @userStory xyz
     * @expectedResults Entities correctly persisted.
     */
    public void testEntitiesWithAndWithoutVersion() throws Exception {
        ParentTestEntity pte = new ParentTestEntity();
        pte.setName("testVersion" + System.currentTimeMillis());
       
        AccountEntity ae = new AccountEntity();
        ae.setName("testVersions" + System.currentTimeMillis());
       
        EntityTransaction tx = em3.getTransaction();
        tx.begin();
        try {
            em3.persist(ae);
            em3.persist(pte);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            Assert.fail(e.getMessage());
        }
       
        tx.begin();
        try {
            ParentTestEntity updatedPte = em3.find(ParentTestEntity.class, pte.getId());
            AccountEntity updatedAe = em3.find(AccountEntity.class, ae.getId());
            updatedPte.setName("testVersions" + System.currentTimeMillis());
            updatedAe.setName("testVersions" + System.currentTimeMillis());

            em3.merge(updatedAe);
            em3.merge(updatedPte);
            tx.commit();
        } catch (Exception e) {
            if (tx.isActive()) {
                tx.rollback();
            }
            String failureMsg = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
            Assert.fail(failureMsg);
        }
    }


    @DataProvider
    public Object[][] txCollectionUpdate() throws NumberFormatException, MalformedURLException {
        Object [][] scenarios = new Object[][]{
            {"Collection update", emfac2},
            {"Collection update a-o-n", emfac3},
        };
        return scenarios;
    }


    @Test(enabled = false, dataProvider = "txCollectionUpdate")
    /**
     * Optimistic transaction with changes in collection properties.
     * Verify correct optimistic transaction behavior for changes in collection valued properties.
     * @hierarchy javasdk
     * @userStory xyz
     * @expectedResults Collection is correctly updated.
     */
    public void testCollectionUpdate(String scenarioName, EntityManagerFactory emf) throws InterruptedException {
        final int numChildren = 3;
        ParentTestEntity parent = new ParentTestEntity();
        parent.init();
        EntityTransaction tx = em.getTransaction();
        if (!tx.isActive()) tx.begin();
        em.persist(parent);
        for (int i = 0; i < numChildren; i++) {
            TestEntity te = new TestEntity();
            JPATestUtils.initializeTestEntity(te);
            te.setParent(parent);
            te.setParentMasterDetail(parent);
            em.persist(te);
            parent.addTestEntity(te);
        }
        em.merge(parent);
        tx.commit();
        Thread.sleep(1000);
        runCollectionUpdateTest(emf, parent, numChildren);
    }

    private void runCollectionUpdateTest(EntityManagerFactory emFac, ParentTestEntity parent, int childCount)
    throws InterruptedException {
        EntityManager e1 = emFac.createEntityManager();
        EntityManager e2 = emFac.createEntityManager();
        ParentTestEntity p1 = e1.find(parent.getClass(), parent.getId());
        ParentTestEntity p2 = e2.find(parent.getClass(), parent.getId());

        EntityTransaction tx1 = e1.getTransaction();
        Assert.assertEquals(p1.getTestEntities().size(), childCount, "Wrong number of children before update");
        Assert.assertEquals(p2.getTestEntities().size(), childCount, "Wrong number of children before update");
        TestEntity newTE = new TestEntity();
        JPATestUtils.initializeTestEntity(newTE);
        newTE.setParent(p1);
        newTE.setParentMasterDetail(p1);
        p1.addTestEntity(newTE);
        /**
         * You have to touch the parent.
         * Adding a child only does not touch the parent
         * since we do not persist parent to child relationship information.
         * Only the child has a foreignkey to the parent so parent object is
         * not touched at all. Therefore there is no chance of optimistic failure
         * on parent if you add a child or modify it.
         */
        p1.setName("Upd1_" + System.currentTimeMillis());
        if (!tx1.isActive()) tx1.begin();
        e1.merge(newTE);
        e1.merge(p1);
        tx1.commit();
        Thread.sleep(1000);
        //re-read and verify update
        p1 = e1.find(parent.getClass(), parent.getId());
        Assert.assertEquals(p1.getTestEntities().size(), childCount + 1, "Wrong number of children after update");
        Assert.assertEquals(p2.getTestEntities().size(), childCount, "Wrong number of children after update on unchanged parent");
        //attempt concurrent update
        p2.setName("Upd2_" + System.currentTimeMillis());
        EntityTransaction tx2 = e2.getTransaction();
        if (!tx2.isActive()) tx2.begin();
        e2.merge(p2);
        try {
            tx2.commit();
            Assert.fail("Expected exception during concurrent update of collection valued property");
        } catch (RollbackException re) {
            Assert.assertTrue(OLE.getClass().isInstance(re.getCause()),
                    "Wrong exception type received: " + re.getCause().getMessage());
            Assert.assertTrue(re.getCause().getMessage().contains(OLE.getMessage()),
                    "Wrong exception message on delete. " + re.getCause().getMessage());
        }
    }

    /**
     * Generates prefix (hopefully) specific to thread. Prefix might be equal if
     * several thread numbers are in the same equivalence class for %25.
     */
    static String getThreadPrefix() {
        int id = Long.valueOf(Thread.currentThread().getId()).intValue();
        if (id > 25) id = id % 25;
        StringBuffer prefix = new StringBuffer();
        for (int i = 0; i < id; i++) {
            prefix.append('|');
        }
        return prefix.toString();
    }

    /**
     * Helper for millisecond time stamps.
     * @param cal
     * @return
     */
    static String getPrintDateMs(Calendar cal) {
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
        int h = cal.get(Calendar.HOUR_OF_DAY);
        int m = cal.get(Calendar.MINUTE);
        int s = cal.get(Calendar.SECOND);
        int ms = cal.get(Calendar.MILLISECOND);
        return h + ":" + m + ":" + s + ":" + ms;
    }
}
TOP

Related Classes of com.force.sdk.jpa.OptimisticTxTestMT$ScriptRunner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.