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


import java.util.HashMap;
import java.util.Map;

import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.persistence.FetchPlan;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.jdbc.common.apps.ComplexEmbeddedPC;
import org.apache.openjpa.persistence.jdbc.common.apps.EmbeddedOwnerPC;
import org.apache.openjpa.persistence.jdbc.common.apps.EmbeddedPC;
import org.apache.openjpa.persistence.jdbc.common.apps.RecursivelyEmbeddedPC;


/**
 * Test various bits of embedded-field functionality.
 * <p/>
 * ##### this should test embedded-element collections, maps, at least to ensure
 * ##### that the state managers of values in embedded collections, maps do not
 * ##### have owners. Not relevant in 3.x; will be important in 4.0.
 */
public class TestEmbeddedPessimisticLocking
    extends TestSQLListenerTestCase {


    private boolean supportsLocking;
    private Object oid;
    private OpenJPAEntityManagerFactory emf;

    public TestEmbeddedPessimisticLocking(String name)
    {
    	super(name);
    }

	@Override
    public void setUp() throws Exception{
		super.setUp();
		emf = (OpenJPAEntityManagerFactory)getEmf(getProps());
	}



    @Override
    public void setUpTestCase() {

        JDBCConfiguration conf =
            (JDBCConfiguration) ((OpenJPAEntityManagerFactorySPI)
                    OpenJPAPersistence.cast(emf)).getConfiguration();

        supportsLocking =
            conf.getDBDictionaryInstance().supportsSelectForUpdate;

       deleteAll(EmbeddedOwnerPC.class);

        OpenJPAEntityManager em =(OpenJPAEntityManager)currentEntityManager();
        startTx(em);
        EmbeddedOwnerPC owner = new EmbeddedOwnerPC(10, 20);
        em.persist(owner);

        EmbeddedPC embedded = new EmbeddedPC();
        embedded.setIntField(4);
        embedded.setStringField("foo");
        owner.setEmbedded(embedded);

        EmbeddedPC embedded2 = new EmbeddedPC();
        embedded2.setIntField(8);
        embedded2.setStringField("bar");
        ComplexEmbeddedPC complex = new ComplexEmbeddedPC();
        complex.setStringField("complex");
        complex.setEmbedded(embedded2);
        owner.setComplexEmbedded(complex);

        endTx(em);

        oid = em.getObjectId(owner);
        em.close();
    }

    private void prepareEMF(OpenJPAEntityManagerFactory emf) {
        // do this to ensure that the SELECT DISTINCT gets performed here.
        OpenJPAEntityManager em = emf.createEntityManager();

        //FIXME jthomas commenting this since setOptimistic is not available for
        //userTx
        //em.getTransaction().setOptimistic(false);
        startTx(em);

        try {
            EmbeddedOwnerPC pc = (EmbeddedOwnerPC) em.getObjectId(oid);
        } finally {
            rollbackTx(em);
            em.close();
        }
    }

    public void testEmbeddedFieldsWithLockedParent() {
        OpenJPAEntityManager em =(OpenJPAEntityManager)currentEntityManager();
        prepareEMF(em.getEntityManagerFactory());
        ((FetchPlan) em.getFetchPlan()).addField(EmbeddedOwnerPC.class,
                "embedded");
//FIXME jthomas commenting this since setOptimistic is not available for userTx
//        pm.currentTransaction().setOptimistic(false);
        startTx(em);
        try {
            sql.clear();
            EmbeddedOwnerPC pc = (EmbeddedOwnerPC) em.getObjectId(oid);
            assertEquals(1, sql.size());

            pc.getEmbedded().setStringField
                (pc.getEmbedded().getStringField() + "bar");
            // should not go to the db for another lock; we use <=, since
            // some databases (like HSQL) don't support locking at all
            assertTrue(sql.size() <= 1);
        } finally {
            rollbackTx(em);
            em.close();
        }
    }

    public void testEmbeddedFieldsWithUnlockedParent() {
        OpenJPAEntityManager em =(OpenJPAEntityManager)currentEntityManager();
		prepareEMF(em.getEntityManagerFactory());
        ((FetchPlan) em.getFetchPlan()).addField(EmbeddedOwnerPC.class,
                "embedded");


//FIXME jthomas commenting this since setOptimistic is not available for userTx
//        pm.currentTransaction().setOptimistic(false);
        startTx(em);

        try {

            //FIXME jthomas - no equivalent found for LockLevels.LOCK_NONE
            //((FetchPlan) pm.getFetchPlan()).setReadLockMode(
            //    LockLevels.LOCK_NONE);

            sql.clear();
            EmbeddedOwnerPC pc = (EmbeddedOwnerPC) em.getObjectId(oid);
            EmbeddedPC embedded = pc.getEmbedded();

            assertNull(getStateManager(pc, em).getLock());
            assertNull(getStateManager(embedded, em).getLock());
            assertEquals(1, sql.size());
            sql.clear();

            embedded.setStringField(embedded.getStringField() + "bar");
            // should not go to the db for another lock -- should have gotten
            // one along with the embedded field's lock
            assertTrue(sql.size() <= 1);
            assertNotNull(getStateManager(pc, em).getLock());

            // embeddeds don't get locks at all.
            assertNull(getStateManager(embedded, em).getLock());

            // owner is dirtied when embedded record is changed
            assertTrue(getStateManager(pc, em).isDirty());
            assertTrue(getStateManager(embedded, em).isDirty());

            pc.setStringField(pc.getStringField() + "bar");
            // should not go to the db for another lock.
            assertTrue(sql.size() <= 1);
        } finally {
            rollbackTx(em);
            em.close();
        }
    }

    public void testComplexEmbeddedFieldsWithLockedParent() {

        OpenJPAEntityManager em =(OpenJPAEntityManager)currentEntityManager();
		prepareEMF(em.getEntityManagerFactory());
		em.getFetchPlan().setMaxFetchDepth(-1);
        ((FetchPlan) em.getFetchPlan()).addField(EmbeddedOwnerPC.class,
                "complexEmbedded");
        ((FetchPlan) em.getFetchPlan()).addField(RecursivelyEmbeddedPC.class,
                "embedded");

//FIXME jthomas commenting this since setOptimistic is not available for userTx
//        pm.currentTransaction().setOptimistic(false);
        startTx(em);
        try {
            sql.clear();
            EmbeddedOwnerPC pc = (EmbeddedOwnerPC) em.getObjectId(oid);
            assertEquals(1, sql.size());

            pc.getComplexEmbedded().getEmbedded().setStringField
                (pc.getComplexEmbedded().getEmbedded().getStringField() +
                    "bar");
            // should not go to the db for another lock.
            assertTrue(sql.size() <= 1);
        } finally {
            rollbackTx(em);
            em.close();
        }
    }

    public void testComplexEmbeddedFieldsWithUnlockedParent() {
        // doing this because setting the read lock level
        // does not seem to be disabling FOR UPDATE.


        OpenJPAEntityManager em =(OpenJPAEntityManager)currentEntityManager();
		prepareEMF(em.getEntityManagerFactory());
		em.getFetchPlan().setMaxFetchDepth(-1);
        ((FetchPlan) em.getFetchPlan()).addField(EmbeddedOwnerPC.class,
                "complexEmbedded");
        ((FetchPlan) em.getFetchPlan()).addField(RecursivelyEmbeddedPC.class,
                "embedded");



//FIXME jthomas commenting this since setOptimistic is not available for userTx
//        pm.currentTransaction().setOptimistic(false);
        startTx(em);

        try {
            //FIXME jthomas FetchPlan.LOCK_NONE??
            //((FetchPlan) em.getFetchPlan()).setReadLockLevel
            //    (FetchPlan.LOCK_NONE);

            sql.clear();
            EmbeddedOwnerPC pc = (EmbeddedOwnerPC) em.getObjectId(oid);
            ComplexEmbeddedPC complex = pc.getComplexEmbedded();
            EmbeddedPC embedded = complex.getEmbedded();

            assertNull(getStateManager(pc, em).getLock());
            assertNull(getStateManager(complex, em).getLock());
            assertNull(getStateManager(embedded, em).getLock());
            assertEquals(1, sql.size());
            sql.clear();

            embedded.setStringField(embedded.getStringField() + "bar");
            // should not go to the db for another lock -- should have gotten
            // one along with the embedded field's lock
            assertTrue(sql.size() <= 1);
            assertNotNull(getStateManager(pc, em).getLock());

            // embeddeds don't get locks at all.
            assertNull(getStateManager(complex, em).getLock());
            assertNull(getStateManager(embedded, em).getLock());

            // owner is dirtied when embedded record is changed
            assertTrue(getStateManager(pc, em).isDirty());
            assertTrue(getStateManager(complex, em).isDirty());
            assertTrue(getStateManager(embedded, em).isDirty());

            complex.setStringField(complex.getStringField() + "bar");
            // should not go to the db for another lock.
            assertTrue(sql.size() <= 1);

            pc.setStringField(pc.getStringField() + "bar");
            // should not go to the db for another lock.
            assertTrue(sql.size() <= 1);
        } finally {
            rollbackTx(em);
            em.close();
        }
    }

	private Map getProps() {
		Map props=new HashMap();
		props.put("openjpa.DataCache", "true");
		props.put("openjpa.RemoteCommitProvider", "sjvm");
		props.put("openjpa.FlushBeforeQueries", "true");
		props.put("javax.jdo.option.IgnoreCache", "false");
        //propsMap.put("openjpa.BrokerImpl", "kodo.datacache.CacheTestBroker");
        //CacheTestBroker.class.getName());
		return props;
	}


}
