/*
 * TestIncrementalFlushes.java
 *
 * Created on October 12, 2006, 11:24 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.persistence.kernel;



import org.apache.openjpa.event.AbstractTransactionListener;
import org.apache.openjpa.event.TransactionEvent;
import org.apache.openjpa.kernel.PCState;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.kernel.common.apps.
        ModInstanceCallbackTests;
import org.apache.openjpa.persistence.kernel.common.apps.ModRuntimeTest1;

public class TestIncrementalFlushes extends BaseKernelTest {

    public TestIncrementalFlushes(String str) {
        super(str);
    }

    /**
     * Creates a new instance of TestIncrementalFlushes
     */
    public TestIncrementalFlushes() {
    }

    @Override
    public void setUp() {
        deleteAll(ModRuntimeTest1.class);
//        deleteAll(ModInstanceCallbackTests.class);
    }

    public void testBasicJdoPreStore() {
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);
        pm.flush();
        assertTrue(a.preStoreCalled);
        endTx(pm);
    }

    public void testNoFlush() {
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);
        endTx(pm);
        assertTrue(a.preStoreCalled);
    }

    public void testFlushNoChange() {
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);
        pm.flush();
        endTx(pm);
        assertTrue(a.preStoreCalled);
        assertEquals(10, a.getIntField());
    }

    /**
     * Helper method for some common test cases. See utilizations of
     * this below.
     */
    private void basicHelper(boolean update, boolean multi, boolean dfg,
        boolean nonDFG) {
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);

        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);
        if (update) {
            endTx(pm);
            Object oid = pm.getObjectId(a);
            endEm(pm);
            pm = getPM(true, false);
            startTx(pm);
            a = (ModInstanceCallbackTests) pm
                .find(ModInstanceCallbackTests.class, oid);
        } else {
            pm.flush();
        }

        if (dfg)
            a.setIntField(11);
        if (nonDFG)
            a.setNonDFGField(11);

        if (multi) {
            pm.flush();

            if (dfg)
                a.setIntField(12);
            if (nonDFG)
                a.setNonDFGField(12);
        }

        endTx(pm);

        // if no changes were made and we're in update mode, then this
        // object won't have had jdoPreStore() called.
//        if (!(update && (!dfg && !nonDFG)))
//            assertTrue("a.prestoreCalled is false", a.preStoreCalled);

        if (multi) {
            if (dfg)
                assertEquals("a.getIntField is not 12", 12, a.getIntField());
            if (nonDFG)
                assertEquals("a.getNonDFGField is not 12", 12,
                    a.getNonDFGField());
        } else {
            if (dfg)
                assertEquals("a.getIntField is not 12", 11, a.getIntField());
            if (nonDFG)
                assertEquals("a.getNonDFGField is not 12", 11,
                    a.getNonDFGField());
        }
    }

    public void testFlushStorePrimaryDFGChange() {
        basicHelper(false, false, true, false);
        basicHelper(false, true, true, false);
        basicHelper(true, false, true, false);
        basicHelper(true, true, true, false);
    }

    public void testFlushStorePrimaryNonDFGChange() {
        basicHelper(false, false, false, true);
        basicHelper(false, true, false, true);
        basicHelper(true, false, false, true);
        basicHelper(true, true, false, true);
    }

    public void testFlushStorePrimaryNonDFGAndDFGChange() {
        basicHelper(false, false, true, true);
        basicHelper(false, true, true, true);
        basicHelper(true, false, true, true);
        basicHelper(true, true, true, true);
    }

    public void testFlushStorePrimaryNoChanges() {
        basicHelper(false, false, false, false);
        basicHelper(false, true, false, false);
        basicHelper(true, false, false, false);
        basicHelper(true, true, false, false);
    }

    public void testJdoPreStoreWithModificationBeforeFlush() {
        tjpswmHelper(true);
    }

    public void testJdoPreStoreWithModificationAfterFlush() {
        tjpswmHelper(false);
    }

    private void tjpswmHelper(boolean before) {
        // set retainvalues to false so that we can ensure that the
        // data in the database is correct, and that we're not just
        // testing that the JVM data is correct.
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);

        // by setting the name to 'bar', the jdoPreStore() invocation
        // will set the parent to a new object. This ensures that new
        // objects created in jdoPreStore() make their way into the DB
        // during commit.
        if (before) {
            a.setStringField("bar");
            pm.flush();
        } else {
            pm.flush();
            a.setStringField("bar");
        }
        endTx(pm);
        assertTrue("a.preStoreCalled is false", a.preStoreCalled);
        assertNotNull("a.getOneOne is null", a.getOneOne());
        assertTrue("getOneOne().getstrngfld.equals(jdoPrestore) is false",
            a.getOneOne().getStringField().equals("jdoPreStore"));
    }

    public void testOneToOneBefore() {
        totoHelper(true, true, false);
        totoHelper(true, false, false);
        totoHelper(true, true, true);
        totoHelper(true, false, true);
    }

    public void testOneToOneAfter() {
        totoHelper(false, true, false);
        totoHelper(false, false, false);
        totoHelper(false, true, true);
        totoHelper(false, false, true);
    }

    private void totoHelper(boolean before, boolean persist,
        boolean multi) {
        // set retainvalues to false so that we can ensure that the
        // data in the database is correct, and that we're not just
        // testing that the JVM data is correct.
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModInstanceCallbackTests a = new ModInstanceCallbackTests("foo", 10);
        pm.persist(a);

        ModRuntimeTest1 parent = new ModRuntimeTest1("baz", 11);
        if (!before)
            pm.flush();

        if (persist)
            pm.persist(parent);

        a.setOneOne(parent);

        if (before)
            pm.flush();

        ModRuntimeTest1 oldParent = null;
        if (multi) {
            oldParent = parent;
            parent = new ModRuntimeTest1("newParent", 12);

            if (!before)
                pm.flush();

            if (persist)
                pm.persist(parent);

            a.setOneOne(parent);

            if (before)
                pm.flush();
        }

        endTx(pm);
        assertTrue("a.preStoreCalled is false", a.preStoreCalled);
        assertNotNull("a.getOneOne is null", a.getOneOne());
        if (!multi)
            assertTrue("a.getOneOne().getStringField().equals(baz) is false",
                a.getOneOne().getStringField().equals("baz"));
        else {
            assertTrue(
                "a.getOneOne().getStringField().equals(newParent) is false",
                a.getOneOne().getStringField().equals("newParent"));

            // if multi, then we really should delete the baz
            // parent. This isn't happening right now.
            // ### should be a bug
            //assertTrue (JDOHelper.isDeleted (oldParent));
        }
    }

    private void assertState(Object o, PCState state, OpenJPAEntityManager pm) {
        assertEquals(state, getStateManager(o, pm).getPCState());
    }

    private void commitAndTestDelete(OpenJPAEntityManager pm, Object o) {
        Object oid = pm.getObjectId(o);
        endTx(pm);

        pm = getPM();
        try {
            pm.find(Object.class, oid);
            fail("should not be able to load deleted object");
        } catch (Exception e) {
            // expected case
        }
    }

    public void testDeleteNew() {
        OpenJPAEntityManager pm = getPM(true, false);
        startTx(pm);
        ModRuntimeTest1 a = new ModRuntimeTest1("foo", 10);
        pm.persist(a);
        pm.remove(a);
        assertState(a, PCState.PNEWDELETED, pm);
    }

    public void testOptimisticLockGivesCorrectError() {
        OpenJPAEntityManager pm1 = getPM(true, false);
        OpenJPAEntityManager pm2 = getPM(true, false);

        ModRuntimeTest1 a1 = new ModRuntimeTest1("foo", 10);
        startTx(pm1);
        pm1.persist(a1);
        endTx(pm1);

        ModRuntimeTest1 a2 = (ModRuntimeTest1)
            pm2.find(ModRuntimeTest1.class, pm2.getObjectId(a1));
        startTx(pm2);
        a2.setStringField("foobar");
        endTx(pm2);

        startTx(pm1);
        a1.setStringField("foobarbaz");
        try {
            endTx(pm1);
        } catch (Exception ole) {
            // expected case
        } finally {
            rollbackTx(pm1);

            pm1.close();
            pm2.close();
        }
    }

    /**
     * Verify that flushes to the datastore are isolated from other
     * PersistenceManagers. This is mostly a test of the underlying
     * datastore's transactional isolation capabilities.
     * <p/>
     * Disabled: this hangs on Sybase.
     */
    public void XXXtestFlushesAreIsolated() {
        final String name = "testFlushesAreIsolated";

        deleteAll(ModRuntimeTest1.class);

        OpenJPAEntityManager flushPM = getPM(true, false);
        startTx(flushPM);

        OpenJPAEntityManager readPM = getPM(true, false);
        startTx(readPM);

        assertSize(0, flushPM.createNativeQuery("stringField == '" + name + "'",
            ModRuntimeTest1.class));
        assertSize(0, readPM.createNativeQuery("stringField == '" + name + "'",
            ModRuntimeTest1.class));

        ModRuntimeTest1 a = new ModRuntimeTest1(name, randomInt());

        flushPM.persist(a);

        assertSize(0, readPM.createNativeQuery("name == '" + name + "'",
            ModRuntimeTest1.class));

        flushPM.flush();

        // make sure the other pm doesn't see the flushed object
        assertSize(0, readPM.createNativeQuery("name == '" + name + "'",
            ModRuntimeTest1.class));

        flushPM.remove(a);

        assertSize(0, flushPM.createNativeQuery("name == '" + name + "'",
            ModRuntimeTest1.class));
        assertSize(0, readPM.createNativeQuery("name == '" + name + "'",
            ModRuntimeTest1.class));

        endTx(flushPM);
        endEm(flushPM);

        endTx(readPM);
        endEm(readPM);
    }

    public void testEmptyFlush() {
        OpenJPAEntityManager pm = getPM();
        TListener listener = new TListener();
        ((OpenJPAEntityManagerSPI) pm).addTransactionListener(listener);
        startTx(pm);
        ModRuntimeTest1 pc = new ModRuntimeTest1();
        pm.persist(pc);
        pm.flush();
        assertEquals(1, listener.flushes);
        assertEquals(0, listener.commits);

        pm.flush();
        assertEquals(1, listener.flushes);
        assertEquals(0, listener.commits);

        pc.setIntField(3);
        pm.flush();
        assertEquals(2, listener.flushes);
        assertEquals(0, listener.commits);

        endTx(pm);
        assertEquals(2, listener.flushes);
        assertEquals(1, listener.commits);

        endEm(pm);
    }

    public void testEmptyRollback() {
        OpenJPAEntityManager pm = getPM();
        TListener listener = new TListener();
        ((OpenJPAEntityManagerSPI) pm).addTransactionListener(listener);
        startTx(pm);
        pm.flush();
        rollbackTx(pm);
        assertEquals(0, listener.flushes);
        assertEquals(0, listener.commits);
        endEm(pm);
    }

    public void testEmptyCommit() {
        OpenJPAEntityManager pm = getPM();
        TListener listener = new TListener();
        ((OpenJPAEntityManagerSPI) pm).addTransactionListener(listener);
        startTx(pm);
        endTx(pm);
        assertEquals(0, listener.flushes);
        assertEquals(1, listener.commits);
        endEm(pm);
    }

    private static class TListener
        extends AbstractTransactionListener {

        public int flushes = 0;
        public int commits = 0;

        @Override
        protected void eventOccurred(TransactionEvent event) {
            if (event.getType() == TransactionEvent.BEFORE_FLUSH)
                flushes++;
            else if (event.getType() == TransactionEvent.BEFORE_COMMIT)
                commits++;
        }
    }
}
