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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import jakarta.persistence.EntityManager;

import org.apache.openjpa.datacache.ConcurrentDataCache;
import org.apache.openjpa.datacache.DataCache;
import org.apache.openjpa.datacache.DataCacheManager;
import org.apache.openjpa.datacache.DelegatingDataCache;
import org.apache.openjpa.datacache.QueryCache;
import org.apache.openjpa.datacache.TypesChangedEvent;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.PCData;
import org.apache.openjpa.kernel.jpql.JPQLParser;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.Extent;
import org.apache.openjpa.persistence.JPAFacadeHelper;
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.OpenJPAQuery;
import org.apache.openjpa.persistence.common.utils.AbstractTestCase;
import org.apache.openjpa.persistence.datacache.common.apps.AppIdCacheObject;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectA;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectAChild1;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectAChild2;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectB;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectBChild1;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectC;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectD;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectE;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectF;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectG;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectH;
import org.apache.openjpa.persistence.datacache.common.apps.
        CacheObjectInterface;
import org.apache.openjpa.persistence.datacache.common.apps.CacheObjectJ;
import org.apache.openjpa.persistence.datacache.common.apps.RuntimeTest1;
import org.apache.openjpa.util.CacheMap;
import org.apache.openjpa.util.Id;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.proxy.ProxyDate;

import junit.framework.AssertionFailedError;

/**
 * ### should add 1..1 relation test ### app id compound key test
 */
public abstract class CacheTest extends AbstractTestCase {

    private static String ORIG_NAME = "origName";

    private static String NEW_NAME = "newName";

    private static int ORIG_AGE = 30;

    private static String ORIG_PARENT_NAME = "origParentName";

    private static int ORIG_PARENT_AGE = 31;

    private OpenJPAEntityManagerFactory timeoutFactory = null;

    private OpenJPAEntityManagerFactory factory = null;

    private OpenJPAEntityManagerFactory factory2 = null;

    private MetaDataRepository repos;

    private Object oid;

    private Object parentOid;

    private Object oidwithclass;

    private OpenJPAEntityManager em;

    private CacheObjectA a;

    public CacheTest(String test) {
        super(test, "datacachecactusapp");
    }

    public void clear() throws Exception {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            Class[] toDelete = new Class[]{ CacheObjectA.class,
                CacheObjectB.class, CacheObjectC.class, CacheObjectD.class,
                CacheObjectE.class, CacheObjectJ.class,
                AppIdCacheObject.class, };
            for (Class aClass : toDelete) {
                startTx(em);
                Extent e = em.createExtent(aClass, true);
                Iterator it = e.iterator();
                while (it.hasNext()) {
                    em.remove(it.next());
                }
                endTx(em);
            }
        }
        catch (OpenJPAException jpae) {
            Throwable[] ts = jpae.getNestedThrowables();
            for (int i = 0; ts != null && i < ts.length; i++) {
                ts[i].printStackTrace();
            }
//			jpae.printStackTrace();

        }
        finally {
            endEm(em);
        }
    }

    /**
     * Return a string array of extra configuration options for the specified
     * cache.
     */
    protected abstract String[] getConfs();

    /**
     * Return a string array of extra configuration options for a second cache.
     */
    protected abstract String[] getConfs2();

    /**
     * Return true if this cache is a coherent one (one where changes in one
     * cache are immediately visible elsewhere); otherwise returns false. In the
     * context of this test class, coherence is a single-JVM thing only.
     */
    protected boolean isCacheCoherent() {
        return false;
    }

    @Override
    public void setUp() throws Exception {

        /*
         * OpenJPA does not seem to support plural configuration properties.  (Although it seems
         * that Kodo does...)  Until OpenJPA is updated to support this multiple configuration
         * setting, the following configuration item will be disabled...
         *
         * Specifically, this type of configuration is currently not allowed...
         * <property name="openjpa.DataCache" value="true, true(Name=xxx)"/>
         */
        String[] confs = getConfs();
//        for (int i = 0; i < confs.length; i = i + 2) {
//            if ("openjpa.DataCache".equals(confs[i]))
//                confs[i + 1] +=
//                    ", true(Name=not-the-default-cache, CacheSize=10)";
//        }
//
        String[] confs2 = getConfs2();
//        for (int i = 0; i < confs2.length; i = i + 2) {
//            if ("openjpa.DataCache".equals(confs2[i]))
//                confs2[i + 1] +=
//                    ", true(Name=not-the-default-cache, CacheSize=10)";
//        }

        Map propsMap1 = new HashMap();
        for (int i = 0; i < confs.length; i += 2) {
            propsMap1.put(confs[i], confs[i + 1]);
        }
        Map propsMap2 = new HashMap();
        for (int i = 0; i < confs2.length; i += 2) {
            propsMap2.put(confs2[i], confs2[i + 1]);
        }

        factory = (OpenJPAEntityManagerFactory) getEmf(propsMap1);
        factory2 = (OpenJPAEntityManagerFactory) getEmf(propsMap2);

        repos = JPAFacadeHelper.toBrokerFactory(factory).getConfiguration()
            .getMetaDataRepositoryInstance();

        String[] biggerConfs = new String[confs.length + 2];
        System.arraycopy(confs, 0, biggerConfs, 0, confs.length);
        biggerConfs[biggerConfs.length - 2] = "openjpa.DataCacheTimeout";
        biggerConfs[biggerConfs.length - 1] = "1000";
        Map propsMap3 = new HashMap();
        for (int i = 0; i < biggerConfs.length; i += 2) {
            propsMap3.put(biggerConfs[i], biggerConfs[i + 1]);
        }
        timeoutFactory = (OpenJPAEntityManagerFactory) getEmf(propsMap3);

        clear();

        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();

        CacheObjectA a;
        CacheObjectA aparent;
        try {
            // we can't specify this for UserTransaction
            /*
                * pm.currentTransaction().setNontransactionalRead(true);
                * pm.currentTransaction().setOptimistic(true);
                */

//			em.setNontransactionalRead(true);
//			em.setOptimistic(true);

            a = new CacheObjectA(ORIG_NAME, ORIG_AGE);
            aparent = new CacheObjectA(ORIG_PARENT_NAME, ORIG_PARENT_AGE);
            a.setRelatedObject(aparent);
            LinkedList children = new LinkedList();
            children.add(a);
            aparent.setRelatedCollection(children);

            startTx(em);
            em.persist(a);
            em.persist(aparent);
            oid = em.getObjectId(a);
            oidwithclass = new Id(CacheObjectA.class, oid.toString());
            parentOid = em.getObjectId(aparent);
            endTx(em);
        }
        finally {
            endEm(em);
        }

        // load an object in a separate pm before the update
        // happens. This should not change, else we're in
        // violation of the spec.
        this.em = factory.createEntityManager();
        startTx(this.em);
        try {
            // OpenJPAEntityManager openEm=(OpenJPAEntityManager) this.em;
            this.a = (CacheObjectA) this.em.find(CacheObjectA.class, oid);

            // load the parent for testCollections().
            CacheObjectA rel = this.a.getRelatedObject();
            rel.getRelatedCollection();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            endTx(this.em);
            // endEm(this.em);
        }

        em = factory.createEntityManager();
        try {
            startTx(em);
            a = (CacheObjectA) em.find(CacheObjectA.class, oid);
            a.setName(NEW_NAME);

            aparent = (CacheObjectA) em.find(CacheObjectA.class, parentOid);

            CacheObjectA a2 = new CacheObjectA(ORIG_NAME, ORIG_AGE);
            a2.setRelatedObject(aparent);
            aparent.getRelatedCollection().add(a2);
            em.persist(a2);
            endTx(em);

            assertNew(a);
        }
        finally {
            endEm(em);
        }
    }

    @Override
    public void tearDown() throws Exception {
        endEm(em);
        em = null;
        repos = null;
        try {
            closeEMF(factory);
        }
        catch (Exception e) {
        }
        factory = null;
        try {
            closeEMF(factory2);
        }
        catch (Exception e) {
        }
        factory2 = null;
        try {
            closeEMF(timeoutFactory);
        }
        catch (Exception e) {
        }
        super.tearDown();
        timeoutFactory = null;
        oid = null;
        parentOid = null;
        a = null;
    }

    public void testDeletedOneToOneRelations() throws Exception {
        EntityManager em = factory.createEntityManager();
        try {
            startTx(em);
            CacheObjectA a = (CacheObjectA) em.find(CacheObjectA.class, oid);
            assertNotNull(a.getRelatedObject());
            em.remove(a.getRelatedObject());
            endTx(em);
        }
        finally {
            endEm(em);
        }

        EntityManager em2 = factory.createEntityManager();
        try {
            CacheObjectA a2 = (CacheObjectA) em2.find(CacheObjectA.class, oid);
            assertNull(a2.getRelatedObject());
        }
        finally {
            endEm(em2);
        }
    }

    public void testCanCacheExtension() throws Exception {
        DataCache cache = cacheManager(factory).getSystemDataCache();

        // first, test caching of newly created objects.
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        Object o;
        Object oid;
        try {
            startTx(em);
            o = new CacheObjectB("foo");
            em.persist(o);
            endTx(em);
            oid = em.getObjectId(o);
            assertNotNull(oid);
            assertNull(cache.get(oid));
        }
        finally {
            endEm(em);
        }

        // now, test caching of data loaded from the data store.
        em = factory.createEntityManager();
        try {
            o = em.find(CacheObjectB.class, oid);
            assertNotNull(o);
            assertNull(cache.get(oid));
        }
        finally {
            endEm(em);
        }
    }

    public void testGetCache() {
        // first, test caching of newly created objects.
        DataCache defaultCache = cacheManager(factory).getDataCache(
            DataCache.NAME_DEFAULT, false);
        assertNotNull(defaultCache);

        DataCache cache = cacheManager(factory).getSystemDataCache();
        assertEquals(defaultCache, cache);

        ClassMetaData aMeta = repos.getMetaData(CacheObjectA.class, null, true);
        ClassMetaData aChild1Meta = repos.getMetaData(CacheObjectAChild1.class,
            null, true);
        ClassMetaData aChild2Meta = repos.getMetaData(CacheObjectAChild2.class,
            null, true);
        ClassMetaData bMeta = repos.getMetaData(CacheObjectB.class, null, true);
        ClassMetaData bChild1Meta = repos.getMetaData(CacheObjectBChild1.class,
            null, true);
        ClassMetaData cMeta = repos.getMetaData(CacheObjectC.class, null, true);
        ClassMetaData dMeta = repos.getMetaData(CacheObjectD.class, null, true);
        ClassMetaData eMeta = repos.getMetaData(CacheObjectE.class, null, true);

        cache = aMeta.getDataCache();
        assertEquals(defaultCache, cache);
        System.out.println("******DataCacheName:"
            + aChild2Meta.getDataCacheName());
        assertNull(aChild2Meta.getDataCache());

        assertNull(bMeta.getDataCache());

        assertEquals(cMeta.getDataCache(), dMeta.getDataCache());
        if (dMeta.getDataCache() instanceof ConcurrentDataCache) {
            ConcurrentDataCache dCacheImpl =
                (ConcurrentDataCache) dMeta.getDataCache();
            assertEquals(10, dCacheImpl.getCacheSize());
        }
        assertEquals(aMeta.getDataCache(), eMeta.getDataCache());
    }

    public void testPrimitives() throws Exception {
        // make sure that the 'a' that was found before changes
        // were made is still valid.
        assertOld(a);
        em.refresh(a);
        assertNew(a);
    }

    // FIXME Seetha Sep 25,2006
    /*
      * public void testCollections() throws Exception { CacheObjectA parent =
      * (CacheObjectA) em.find(CacheObjectA.class,ORIG_PARENT_NAME);
      * assertEquals(1, parent.getRelatedCollection().size());
      * em.refresh(parent); assertEquals(2,
      * parent.getRelatedCollection().size()); }
      */

    // FIXME Seetha Sep 25,2006
    /*
      * public void testExpiredCollections() { CacheObjectA parent =
      * (CacheObjectA) em.find(CacheObjectA.class,ORIG_PARENT_NAME);
      * em.refresh(parent); Collection relatedOids = new HashSet();
      * for (Iterator iter = parent.getRelatedCollection().iterator();
      *     iter.hasNext();) {
      * relatedOids.add(JDOHelper.getObjectId(iter.next())); }
      *
      * ClassMetaData meta = repos.getMetaData(CacheObjectA.class, null, true);
      * DataCache cache = meta.getDataCache();
      *  // drop the related data from the cache for (Iterator iter =
      * relatedOids.iterator(); iter.hasNext();) cache.remove(iter.next());
      *
      * PersistenceManager pm2 = factory.getPersistenceManager(); try {
      * assertTrue(cache.contains(parentOid)); parent = (CacheObjectA)
      * pm2.getObjectById(parentOid, true);
      *
      * try { for (Iterator iter = relatedOids.iterator(); iter.hasNext();)
      * assertFalse(cache.contains(iter.next())); } catch (AssertionFailedError
      * e) { bug(467, "data cache can over-eagerly load relation data"); } }
      * finally { close(pm2); } }
      */

    public void testExpiredRelations() {
        CacheObjectA a = (CacheObjectA) em.find(CacheObjectA.class, oid);
        em.refresh(a);
        Object relationOid = em.getObjectId(a.getRelatedObject());
        relationOid = new Id(CacheObjectA.class, relationOid.toString());

        ClassMetaData meta = repos.getMetaData(CacheObjectA.class, null, true);
        DataCache cache = meta.getDataCache();

        // drop the related data from the cache
        cache.remove(relationOid);

        OpenJPAEntityManager em2 = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            assertTrue(cache.contains(oidwithclass));
            //a = (CacheObjectA) em2.find(CacheObjectA.class, oid);

            try {
                assertFalse(cache.contains(relationOid));
            }
            catch (AssertionFailedError e) {
                // bug(467, "data cache can over-eagerly load relation data");
                /*
                 * I don't think this is a bug, nor should this exception
                 * occur.  Since we're doing a find() operation above and this
                 * field (RelatedObj) has a default Fetch type of EAGER, then
                 * we should be re-loading the RelatedObj and it will be put back
                 * in the cache...  So, by commenting out the above find()
                 * operation (or overriding the default Fetch type to EAGER), then
                 * this assertFalse works...
                 */
                e.printStackTrace();
            }
        }
        finally {
            endEm(em2);
        }
    }

    public void testPCArrays() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(newEm);
            CacheObjectA parent = (CacheObjectA) newEm.find(CacheObjectA.class,
                parentOid);
            CacheObjectA a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            a.setRelatedArray(new CacheObjectA[]{ parent, a });
            endTx(newEm);
        }
        finally {
            endEm(newEm);
        }

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        try {
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            CacheObjectA[] array = a.getRelatedArray();
            assertEquals(2, array.length);
            assertTrue(array[0] instanceof CacheObjectA);
            assertTrue(array[1] instanceof CacheObjectA);

            Object arrayOid = newEm.getObjectId(array[0]);
            if (!arrayOid.equals(parentOid) && !arrayOid.equals(oid)) {
                fail("array does not contain correct oids");
            }

            arrayOid = newEm.getObjectId(array[1]);
            if (!arrayOid.equals(parentOid) && !arrayOid.equals(oid)) {
                fail("array does not contain correct oids");
            }
        }
        finally {
            endEm(newEm);
        }
    }

    public void testStringArrays() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(newEm);
            CacheObjectA a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            a.setStringArray(new String[]{ "string0", "string1", "string2" });
            endTx(newEm);
        }
        finally {
            endEm(newEm);
        }

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        try {
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            String[] array = a.getStringArray();
            assertEquals(3, array.length);
            assertEquals("string0", array[0]);
            assertEquals("string1", array[1]);
            assertEquals("string2", array[2]);
        }
        finally {
            endEm(newEm);
        }
    }

    public void testPrimitiveArrays() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(newEm);
            CacheObjectA a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            a.setPrimitiveArray(new float[]{ 0, 1, 2 });
            endTx(newEm);
        }
        finally {
            endEm(newEm);
        }

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        try {
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            float[] array = a.getPrimitiveArray();
            assertEquals(3, array.length);
            assertEquals(0.0F, array[0], 0);
            assertEquals(1.0F, array[1], 0);
            assertEquals(2.0f, array[2], 0);
        }
        finally {
            endEm(newEm);
        }
    }

    public void testDateArrays() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        CacheObjectA a;
        Date[] dateArray;
        try {
            startTx(newEm);
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            dateArray = new Date[]{ new Date(), new Date(), new Date() };
            a.setDateArray(dateArray);
            endTx(newEm);
        }
        finally {
            endEm(newEm);
        }

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        try {
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            Date[] array = a.getDateArray();
            if (array[0] == dateArray[0]) {
                fail("date objects are the same");
            }
        }
        finally {
            endEm(newEm);
        }
    }

    public void testDate() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        CacheObjectA a;
        Date d;
        try {
            startTx(newEm);
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            d = new Date();
            a.setDate(d);
            endTx(newEm);
        }
        finally {
            endEm(newEm);
        }

        // sleep a bit so we can ensure that the date doesn't just
        // happen to be the same.
        Thread.sleep(100);

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        try {
            a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
            Date d2 = a.getDate();
            if (d == d2) {
                fail("date objects are the same");
            }

            assertEquals(d.getTime(), d2.getTime());
        }
        finally {
            endEm(newEm);
        }
    }

    public void testLocale() throws Exception {
        OpenJPAEntityManager newEm = (OpenJPAEntityManager) factory
            .createEntityManager();
        startTx(newEm);
        CacheObjectA a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
        Locale l = Locale.getDefault();
        a.setLocale(l);
        endTx(newEm);

        OpenJPAEntityManager newEm2 = (OpenJPAEntityManager) factory
            .createEntityManager();

        a = (CacheObjectA) newEm2.find(CacheObjectA.class, oid);
        Locale l2 = a.getLocale();
        // locales are immutable and final, so the different cached
        // copies should be ==.
        if (l != l2) {
            fail("locale objects are not the same.");
        }

        endEm(newEm);
        endEm(newEm2);
    }

    // ---------- Test query caching ----------
    // * FCOs as params
    // * multi-threaded stuff
    // * access path stuff (see also TestQueryAccessPath)
    // * serializability of returned lists
    // * PM.setQueryCacheEnabled (false);
    // * Query.setQueryCacheEnabled (false);
    // * Pessimistic transactions

    public void testBasicQuery() {
        basicQueries(factory.createEntityManager(), Boolean.FALSE, 3, 1);
        basicQueries(factory.createEntityManager(), Boolean.TRUE, 3, 1);

        // manually notify the cache of changes
        QueryCache cache = cacheManager(factory).getSystemQueryCache();

        // test to see if modifying B causes A's query cache to be flushed
        Set s = new HashSet();
        s.add(CacheObjectB.class);
        cache.onTypesChanged(new TypesChangedEvent(this, s));
        basicQueries(factory.createEntityManager(), Boolean.TRUE, 3, 1);

        // test to see if modifying A causes A's query cache to be flushed
        s.add(CacheObjectA.class);
        cache.onTypesChanged(new TypesChangedEvent(this, s));
        basicQueries(factory.createEntityManager(), Boolean.FALSE, 3, 1);

        // make sure that non-manual notification works
        EntityManager em = factory.createEntityManager();
        try {
            startTx(em);
            CacheObjectA a = new CacheObjectA(ORIG_NAME, ORIG_AGE);
            em.persist(a);
            endTx(em);
        }
        finally {
            endEm(em);
        }

        basicQueries(factory.createEntityManager(), Boolean.FALSE, 4, 2);
    }

    protected void basicQueries(EntityManager em, Boolean inCache, int allSize,
        int origSize) {
        try {
            long start;
            long q1p1;
            long q1p2;
            long q2p1;
            long q2p2;

            Broker broker = JPAFacadeHelper.toBroker(em);
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectA.class.getSimpleName() + " a");
            q.setCandidateExtent(broker.newExtent(CacheObjectA.class, false));
            start = System.currentTimeMillis();
            assertInCache(q, inCache);
            List l = (List) q.execute();
            iterate(l);
            q1p1 = System.currentTimeMillis() - start;

            assertEquals(allSize, l.size());

            start = System.currentTimeMillis();
            List l2 = (List) q.execute();
            iterate(l2);
            q1p2 = System.currentTimeMillis() - start;
            assertEquals(allSize, l2.size());

            q = broker.newQuery(JPQLParser.LANG_JPQL,
                "select a.name,a.age from "
                    + CacheObjectA.class.getSimpleName()
                    + " a where a.name = :n AND a.age = :a");
            q.setCandidateExtent(broker.newExtent(CacheObjectA.class, false));
            start = System.currentTimeMillis();
            assertInCache(q, inCache, new Object[]{ ORIG_NAME,
                    ORIG_AGE});
            l = (List) q.execute(new Object[]{ ORIG_NAME,
                    ORIG_AGE});
            iterate(l);
            q2p1 = System.currentTimeMillis() - start;

            assertEquals(origSize, l.size());

            start = System.currentTimeMillis();
            l2 = (List) q.execute(new Object[]{ ORIG_NAME,
                    ORIG_AGE});
            iterate(l2);
            q2p2 = System.currentTimeMillis() - start;

            assertEquals(origSize, l2.size());
            // System.out.println ("inCache: " + inCache + ";\t q1p1: " + q1p1
            // + ";\t q1p2: " + q1p2 + ";\t q2p1: " + q2p1 + ";\t q2p2: "
            // + q2p2);
        }
        finally {
            endEm(em);
        }
    }

    public void testNonCacheableClass() {
        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        try {
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectB.class.getSimpleName() + " a");

            Collection c = (Collection) q.execute();
            iterate(c);

            // Query results are no longer dependent on cacheability of an entity.
            assertInCache(q, Boolean.TRUE);
        }
        finally {
            close(broker);
        }
    }

    public void testNonCacheableAccessPath() {
        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        try {
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectA.class.getSimpleName()
                + " a where a.relatedB.str = 'foo'");
            // "relatedB.str == 'foo'");
            q.setCandidateExtent(broker.newExtent(CacheObjectA.class, false));

            Collection c = (Collection) q.execute();
            iterate(c);

         // Query results are no longer dependent on cacheability of an entity.
            assertInCache(q, Boolean.TRUE);
        }
        finally {
            close(broker);
        }
    }

    public void testNonCacheableSubclasses1() {
        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        try {
            // a query on the CacheObjectA class includes an uncacheable
            // class; it should therefore not be cacheable.
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectA.class.getSimpleName() + " a");

            Collection c = (Collection) q.execute();
            iterate(c);

            assertInCache(q, Boolean.FALSE);
        }
        finally {
            close(broker);
        }
    }

    public void testNonCacheableSubclasses2() {
        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        try {
            // a query on the CacheObjectA extent configured without
            // subclasses does not include an uncacheable class; it should
            // therefore be cacheable.
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "select a from "
                + CacheObjectA.class.getSimpleName() + " a");
            q.setCandidateExtent(broker.newExtent(CacheObjectA.class, false));

            Collection c = (Collection) q.execute();
            iterate(c);

            assertInCache(q, Boolean.TRUE);
        }
        finally {
            close(broker);
        }
    }

    public void testCacheNames() {
        assertCacheName(CacheObjectA.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectAChild1.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectAChild2.class, null);
        assertCacheName(CacheObjectB.class, null);
        /*
         * Due to the problem documented in the setup() routine, the following tests are not valid...
         */
//        assertCacheName(CacheObjectBChild1.class, null);// sub-classes should inherit parent's @Cacheable setting
//        assertCacheName(CacheObjectC.class, "not-the-default-cache"); multiple datacache instantiation not working...
//        assertCacheName(CacheObjectD.class, "not-the-default-cache");
        assertCacheName(CacheObjectE.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectF.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectG.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectH.class, DataCache.NAME_DEFAULT);
        assertCacheName(CacheObjectJ.class, DataCache.NAME_DEFAULT);
        assertCacheName(AppIdCacheObject.class, DataCache.NAME_DEFAULT);
    }

    private void assertCacheName(Class cls, String cacheName) {
        ClassMetaData meta = JPAFacadeHelper.getMetaData(factory, cls);
        if (cacheName == null)
            assertNull(meta.getDataCache());
        else {
            assertNotNull(meta.getDataCache());
            assertEquals(cacheName, meta.getDataCache().getName());
        }
    }

    // FIXME Seetha Sep 26,2006
    // not able to replace pm.newQuery(CacheObjectA.class);
    /*
      * public void testQueryAggregates() { PersistenceManager pm =
      * factory.getPersistenceManager(); try { Query q =
      * pm.newQuery(CacheObjectA.class); q.setResult("max (age)"); Object o =
      * q.execute(); assertTrue("o must be instanceof Number", o instanceof
      * Number); } finally { close(pm); } }
      */

    public void testCache2() {
        OpenJPAEntityManager em1 =
            (OpenJPAEntityManager) factory.createEntityManager();
        OpenJPAEntityManager em2 = null;
        DataCache cache;

        try {
            CacheObjectA a1 = (CacheObjectA) em1.find(CacheObjectA.class, oid);

            em2 = (OpenJPAEntityManager) factory2.createEntityManager();
            CacheObjectA a2 = (CacheObjectA) em2.find(CacheObjectA.class, oid);

            // assert that the oid is in factory2's cache
            //MetaDataRepository repos2 = factory2.getConfiguration()
            //    .getMetaDataRepositoryInstance();
            MetaDataRepository repos2 =
                ((((OpenJPAEntityManagerFactorySPI) factory2))
                .getConfiguration()).getMetaDataRepositoryInstance();
            ClassMetaData meta = repos2
                .getMetaData(CacheObjectA.class, em2.getClassLoader(), true);
            cache = meta.getDataCache();
            assertTrue(cache.contains(oidwithclass));

            // modify the object.
            startTx(em1);
            a1.setName(a1.getName() + " changed");
            endTx(em1);
        }
        finally {
            if (em2 != null)
                endEm(em2);
            endEm(em1);
        }

        // if the cache is a coherent one, then the changes should be
        // seen. Otherwise, they should not.
        if (isCacheCoherent() || factory == factory2)
            assertTrue("key " + oid + " was not in cache; should have been",
                cache.contains(oidwithclass));
        else
            assertFalse("key " + oid + " was in cache; should not have been",
                cache.contains(oidwithclass));
    }

    public void testTimeouts1() throws Exception {
        timeoutsTest1(30);
    }

    public void timeoutsTest1(int tries) throws Exception {
        // this crazy for looping stuff is here because we're seeing
        // intermittent failures with the garbage collector kicking in
        // during testing. So, this decreases the chances that that
        // will happen.
        Exception e = null;
        int i;
        for (i = 0; i < tries; i++) {
            try {
                timeoutsHelper(factory);
                // any successes will pass the test
                return;
            }
            catch (Exception ex) {
                e = ex;
            }
        }

        throw e;
    }

    public void testTimeouts2() throws Exception {
        timeoutsTest2(30);
    }

    public void timeoutsTest2(int tries) throws Exception {
        Error e = null;
        for (int i = 0; i < tries; i++) {
            try {
                timeoutsHelper(timeoutFactory);
                // any successes will pass the test
                return;
            }
            catch (AssertionFailedError afe) {
                e = afe;
            }
        }

        throw e;
    }

    private void timeoutsHelper(OpenJPAEntityManagerFactory factory)
        throws Exception {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(em);

            // get starting time for sleep calculations below
            Date startTime = new Date();

            CacheObjectE e = new CacheObjectE("e");
            em.persist(e);

            CacheObjectF f = new CacheObjectF("f");
            em.persist(f);

            CacheObjectG g = new CacheObjectG("g");
            em.persist(g);

            CacheObjectH h = new CacheObjectH("h");
            em.persist(h);

            endTx(em);

            // get post-persist time for sleep calculations below
            Date persistTime = new Date();

            Object[] ids = new Object[4];
            ids[0] = new Id(CacheObjectE.class, em.getObjectId(e).toString());
            ids[1] = new Id(CacheObjectF.class, em.getObjectId(f).toString());
            ids[2] = new Id(CacheObjectG.class, em.getObjectId(g).toString());
            ids[3] = new Id(CacheObjectH.class, em.getObjectId(h).toString());

            // build up some queries to test

            // this one should be only on the superclass, since
            // CacheObjectF has a timeout.
            Broker broker = JPAFacadeHelper.toBroker(em);
            org.apache.openjpa.kernel.Query q1 = broker.newQuery(
                JPQLParser.LANG_JPQL, "select a from "
                + CacheObjectE.class.getSimpleName() + " a");
            q1.setCandidateExtent(broker.newExtent(CacheObjectE.class, false));
            iterate((Collection) q1.execute());
            assertInCache(q1, Boolean.TRUE);

            org.apache.openjpa.kernel.Query q2 = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectF.class.getSimpleName() + " a");
            iterate((Collection) q2.execute());
            assertInCache(q2, Boolean.TRUE);

            Date currentTime = new Date();
            long diff = (currentTime.getTime() - startTime.getTime());
            long diff2 = (currentTime.getTime() - persistTime.getTime());
            long sleep = 0;

            getLog().info("CacheTest.timeoutsHelper() testing all are still in the cache, elapsed time="+diff);
            DataCache cache = cacheManager(factory).getDataCache(
                DataCache.NAME_DEFAULT, false);
            diff = Math.max(diff, diff2);
            if (diff < 450) {
                // all should still be in the cache
                checkCache(cache, ids, new boolean[]{ true, true, true, true });
            } else {
                // need to skip the test on slow systems or when using remote DB connections
                getLog().warn("CacheTest.timeoutsHelper() skipping checkCache(all, <500) because diff=" +
                    diff + " and diff2=" + diff2);
            }

            // should cause h to be dropped (timeout=500)
            currentTime = new Date();
            diff = (currentTime.getTime() - startTime.getTime());
            diff2 = (currentTime.getTime() - persistTime.getTime());
            sleep = Math.min((800 - diff), (800 - diff2));
            if (sleep < 0) {
                // we already missed the window
                getLog().warn("CacheTest.timeoutsHelper() skipping sleep for checkCache(h=500) because sleep="+sleep);
            } else if (sleep > 10) {
                getLog().info("CacheTest.timeoutsHelper() testing h to be dropped by waiting sleep="+sleep);
                Thread.currentThread();
                Thread.sleep(700);
                Thread.yield();
            } else {
                sleep = 0;
            }
            // recalc diff again
            currentTime = new Date();
            diff = (currentTime.getTime() - startTime.getTime());
            diff2 = (currentTime.getTime() - persistTime.getTime());
            diff = Math.max(diff, diff2);
            if (diff > 600 && diff < 900) {
                // only h should be dropped
                checkCache(cache, ids, new boolean[]{ true, true, true, false });
            } else {
                // need to skip the test on slow systems or when using remote DB connections
                getLog().warn("CacheTest.timeoutsHelper() skipping checkCache(h=500) because diff=" + diff +
                    " and diff2=" + diff2);
            }

            // if this run has a default timeout (set to 1 sec in the test
            // case), e should be timed out by this point.
            //boolean eStatus =
            //    !(factory.getConfiguration().getDataCacheTimeout() > 0);
            boolean eStatus = !((((OpenJPAEntityManagerFactorySPI) factory)
                    .getConfiguration()).getDataCacheTimeout() > 0);

            // should cause f to be dropped (timeout=1000)
            currentTime = new Date();
            diff = currentTime.getTime() - startTime.getTime();
            diff2 = (currentTime.getTime() - persistTime.getTime());
            diff = Math.max(diff, diff2);
            sleep = 2000 - diff;
            if (sleep < 0) {
                // we already missed the window
                getLog().warn("CacheTest.timeoutsHelper() skipping sleep for checkCache(f=000) because sleep="+sleep);
            } else if (sleep > 10) {
                getLog().info("CacheTest.timeoutsHelper() testing f to be dropped by waiting sleep="+sleep);
                Thread.currentThread();
                Thread.sleep(sleep);
                Thread.yield();
            } else {
                sleep = 0;
            }
            // recalc diff again
            currentTime = new Date();
            diff = currentTime.getTime() - startTime.getTime();
            diff2 = currentTime.getTime() - persistTime.getTime();
            diff = Math.max(diff, diff2);
            if (diff < 4900) {
                // e is conditional, h and f should be dropped, but not g yet
                checkCache(cache, ids, new boolean[]{ eStatus, false, true, false });
            } else {
                // need to skip the test on slow systems or when using remote DB connections
                getLog().warn("CacheTest.timeoutsHelper() skipping checkCache(f=1000) because diff="+diff);
            }

            // at this point, q2 should be dropped (because its candidate
            // class is CacheObjectF), and q1 might be dropped, depending
            // on whether or not we've got a timeout configured.
            assertInCache(q1, (eStatus) ? Boolean.TRUE : Boolean.FALSE);
            assertInCache(q2, Boolean.FALSE);

            // should cause g to be dropped (timeout=5000)
            currentTime = new Date();
            diff = currentTime.getTime() - startTime.getTime();
            diff2 = currentTime.getTime() - persistTime.getTime();
            diff = Math.max(diff, diff2);
            sleep = 6000 - diff;
            if (sleep > 0) {
                getLog().info("CacheTest.timeoutsHelper() testing g to be dropped by waiting sleep="+sleep);
                Thread.currentThread();
                Thread.sleep(sleep);
                Thread.yield();
            }
            // all of them should be dropped now, since diff > 5000
            checkCache(cache, ids, new boolean[]{ eStatus, false, false, false });
        }
        finally {
            endEm(em);
        }
    }

    public void testQueryTimeouts() throws Exception {
        queryTimeoutsHelper(factory);
        queryTimeoutsHelper(timeoutFactory);
    }

    private void queryTimeoutsHelper(OpenJPAEntityManagerFactory factory)
        throws Exception {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(em);

            // get starting time for sleep calculations below
            Date startTime = new Date();

            CacheObjectE e = new CacheObjectE("e");
            em.persist(e);

            CacheObjectF f = new CacheObjectF("f");
            em.persist(f);

            endTx(em);

            // build up some queries to test
            Broker broker = JPAFacadeHelper.toBroker(em);
            org.apache.openjpa.kernel.Query q1 = broker
                .newQuery(JPQLParser.LANG_JPQL, "SELECT a FROM CacheObjectE a");

            q1.setCandidateExtent(broker.newExtent(CacheObjectE.class, false));
            iterate((Collection) q1.execute());
            assertInCache(q1, Boolean.TRUE);

            org.apache.openjpa.kernel.Query q2 = broker
                .newQuery(JPQLParser.LANG_JPQL, "SELECT a FROM CacheObjectF a");
            iterate((Collection) q2.execute());
            assertInCache(q2, Boolean.TRUE);

            // if this run has a default timeout (set to 1 sec in the test
            // case), e should be timed out by this point.
            //boolean eTimedOut =
            //    factory.getConfiguration().getDataCacheTimeout() > 0;
            boolean eTimedOut =
                ((((OpenJPAEntityManagerFactorySPI) factory).getConfiguration())
                    .getDataCacheTimeout() > 0);

            // should cause f to be dropped.
            Date currentTime = new Date();
            long diff = currentTime.getTime() - startTime.getTime();
            long sleep = 2000 - diff;
            if (sleep > 0) {
                getLog().trace("CacheTest.queryTimeoutsHelper() testing f to be dropped by waiting sleep="+sleep);
                Thread.currentThread();
                Thread.sleep(sleep);
                Thread.yield();
            }

            // at this point, q2 should be dropped (because its candidate
            // class is CacheObjectF), and q1 might be dropped, depending
            // on whether or not we've got a timeout configured.
            assertInCache(q1, (eTimedOut) ? Boolean.FALSE : Boolean.TRUE);
            assertInCache(q2, Boolean.FALSE);
        }
        finally {
            endEm(em);
        }
    }

    public void testQueryImplicitEvictions() throws Exception {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            RuntimeTest1[] helperObjs = new RuntimeTest1[5];
            helperObjs[0] = new RuntimeTest1();
            helperObjs[1] = new RuntimeTest1();
            helperObjs[2] = new RuntimeTest1();
            helperObjs[3] = new RuntimeTest1();
            helperObjs[4] = new RuntimeTest1();
            startTx(em);
            em.persist(helperObjs[0]);
            em.persist(helperObjs[1]);
            em.persist(helperObjs[2]);
            em.persist(helperObjs[3]);
            em.persist(helperObjs[4]);
            endTx(em);

            DataCache cache = cacheManager(factory).getDataCache(
                DataCache.NAME_DEFAULT, false);

            if (!isOpenJPACache(cache)) {
                bug(627, "Tangosol cache impl needs modernization");
                return;
            }

            if (cache instanceof DelegatingDataCache)
                cache = ((DelegatingDataCache) cache).getInnermostDelegate();
            if (cache instanceof ConcurrentDataCache) {
                CacheMap map = ((ConcurrentDataCache) cache).getCacheMap();
                map.setCacheSize(3);
                map.setSoftReferenceSize(0);
            }

            startTx(em);
            CacheObjectH h = new CacheObjectH("h");
            em.persist(h);
            CacheObjectJ j = new CacheObjectJ("j", h);
            em.persist(j);
            endTx(em);
            Object hoid = em.getObjectId(h);
            Object joid = em.getObjectId(j);

            Object hoidwithclass = new Id(CacheObjectH.class, hoid.toString());
            Object joidwithclass = new Id(CacheObjectJ.class, joid.toString());
            endEm(em);

            // make sure j and h are in cache; may not be if not LRU
            int attempts = 0;
            for (; attempts < 100 && !cache.contains(joidwithclass); attempts++)
            {
                em = factory.createEntityManager();
                if (!cache.contains(hoidwithclass))
                    em.find(CacheObjectH.class, hoid);
                if (!cache.contains(joidwithclass))
                    em.find(CacheObjectJ.class, joid);
                endEm(em);
            }
            assertTrue("Could not get queried objects into cache",
                attempts < 100);

            // build up a query that uses H in its access path...
            em = factory.createEntityManager();
            Broker broker = JPAFacadeHelper.toBroker(em);
            org.apache.openjpa.kernel.Query q = broker.newQuery(
                JPQLParser.LANG_JPQL, "Select a FROM "
                + CacheObjectJ.class.getSimpleName()
                + " a where a.str = 'h'");
            iterate((Collection) q.execute());
            assertInCache(q, Boolean.TRUE);
            endEm(em);

            // force h out of the cache. we might have to try multiple times
            // if the cache is not LRU
            attempts = 0;
            for (; attempts < 100 && cache.contains(joidwithclass); attempts++)
            {
                em = factory.createEntityManager();
                for (int i = 0; i < 5; i++)
                    em.find(RuntimeTest1.class, em.getObjectId(helperObjs[i]));
                endEm(em);
            }
            assertTrue("Could not kick queried objects out of cache",
                attempts < 100);

            /*
             * Not a valid test...  At least not with the current implementation...
             *
             * Just removing items from the DataCache (as done via the previous loop) is not sufficient
             * to remove the entries from the QueryCache.  Currently, this notification is done at the end
             * of a transaction after inserts, updates, and deletes have been performed.  Then, the
             * updateCaches() method is invoked on the DataCacheStoreManager which will flow the request to
             * the QueryCache.  With no direct updates to the "Entities of interest", then there's nothing to
             * flow over to the QueryCache for cleanup.  Even putting the above loop within a transaction is
             * not sufficient, since there have been no updates to the "Entities of interest".
             */
//            em = factory.createEntityManager();
//            broker = JPAFacadeHelper.toBroker(em);
//            q = broker.newQuery(JPQLParser.LANG_JPQL, "Select a FROM "
//                + CacheObjectJ.class.getSimpleName()
//                + " a where a.str = 'h'");
//            try {
//                assertInCache(q, null);
//            }
//            catch (AssertionFailedError e) {
//                bug(626, "query cache invalidation is broken");
//            }

            // ### should test remote events causing queries to evict.
        }
        finally {
            endEm(em);
        }
    }

    // FIXME SEetha Sep 26,2006
    // not able to replace pm.newQuery(CacheObjectE.class);
    /*
      * public void testAllegedQueryOrderingChanges() throws Exception { //
      * inspired by tsc 3013. pcl: I have not been able to get this // test case
      * to actually fail. However, during analysis of // 3013's stack traces, I
      * discovered that the // QueryKey.equals() method did not deal with the
      * ordering // field correctly, possibly causing the problem.
      *
      * OpenJPAEntityManager em = (OpenJPAEntityManager)
      * factory.createEntityManager(); try { startTx(em,
      * ()); CacheObjectE e = new CacheObjectE("e"); em.persist(e);
      * endTx(em); } finally {
      * endEm(em); }
      *
      * em = factory.createEntityManager(); Query q; Collection c; List l; try {
      * q = em.createQuery(CacheObjectE.class); q.setOrdering("str ascending");
      * c = (Collection) q.execute(); l = new LinkedList(c);
      * assertEquals(1, c.size()); } finally { endEm(em); }
      *
      * em = factory.createEntityManager(); try { q =
      * em.createQuery(CacheObjectE.class); q.setOrdering("str ascending"); c =
      * (Collection) q.execute(); l = new LinkedList(c); assertEquals(1,
      * c.size()); } finally { endEm(em); }
      *
      * try { em = factory.createEntityManager(); q =
      * em.createQuery(CacheObjectE.class); q.setOrdering("str descending"); c =
      * (Collection) q.execute(); assertEquals(1, c.size()); l = new
      * LinkedList(c); } finally { endEm(em); } }
      */

    public void testAllegedConcurrentModificationException() throws Exception {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        CacheObjectE e;
        try {
            ClassMetaData meta = JPAFacadeHelper.getMetaData(em,
                CacheObjectE.class);
            if (!isOpenJPACache(meta.getDataCache()))
                return;

            startTx(em);
            e = new CacheObjectE("e");
            em.persist(e);
            endTx(em);
        }
        finally {
            endEm(em);
        }

        em = factory.createEntityManager();
        try {
            startTx(em);

            // find the new object...
            OpenJPAQuery q = em.createQuery("select a FROM "
                + CacheObjectE.class.getSimpleName()
                + " a where a.str = 'e'");
            e = (CacheObjectE) ((Collection) q.getResultList()).iterator()
                .next();

            // ... and modify the changed object.
            e.setStr("e2");
            e.setStr("e3");
            endTx(em);
        }
        finally {
            endEm(em);
        }
    }

    private boolean isOpenJPACache(DataCache cache) {
        if (cache instanceof DelegatingDataCache)
            cache = ((DelegatingDataCache) cache).getInnermostDelegate();

        return cache instanceof ConcurrentDataCache;
    }

    // ---------- utility methods ----------

    private void checkCache(DataCache cache, Object[] ids, boolean[] stati) {
        CacheTestHelper.checkCache(this, cache, ids, stati);
    }

    private void assertInCache(org.apache.openjpa.kernel.Query q,
        Boolean inCache) {
        CacheTestHelper.assertInCache(this, q, inCache);
    }

    private void assertInCache(org.apache.openjpa.kernel.Query q,
        Boolean inCache, Object[] args) {
        CacheTestHelper.assertInCache(this, q, inCache, args);
    }

    private void iterate(Collection c) {
        CacheTestHelper.iterate(c);
    }

    public void testInterface() throws Exception {
        OpenJPAEntityManager newEm =
            (OpenJPAEntityManager) factory.createEntityManager();
        startTx(newEm);
        CacheObjectA a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);
        CacheObjectC c = new CacheObjectC("blah");
        a.setRelatedInterface(c);
        endTx(newEm);
        Object cId = newEm.getObjectId(c);
        endEm(newEm);

        newEm = (OpenJPAEntityManager) factory.createEntityManager();
        a = (CacheObjectA) newEm.find(CacheObjectA.class, oid);

        CacheObjectInterface c2 = a.getRelatedInterface();
        assertNotNull(c2);

        assertEquals(cId, newEm.getObjectId(c2));
    }

    public void testQueriesOnCollectionsDontUseCache() {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        try {
            startTx(em);
            CacheObjectE e = new CacheObjectE("e");
            em.persist(e);
            endTx(em);
        }
        finally {
            endEm(em);
        }

        em = (OpenJPAEntityManager) factory.createEntityManager();
        OpenJPAQuery q;
        Collection c;
        try {
            q = em.createQuery("select a FROM "
                + CacheObjectE.class.getSimpleName()
                + " a where a.str = 'e'");
            c = new ArrayList((Collection) q.getResultList());
            assertEquals(1, c.size());
            q.closeAll();
        }
        finally {
            endEm(em);
        }

        try {
            em = (OpenJPAEntityManager) factory.createEntityManager();
            q = em.createQuery("select a FROM "
                + CacheObjectE.class.getSimpleName()
                + " a where a.str = 'e'");
            q.setCandidateCollection(new ArrayList(0));
            c = (Collection) q.getResultList();
            assertEquals(0, c.size());
            q.closeAll();
        }
        finally {
            endEm(em);
        }
    }

    public void testDFGFieldsLoaded1() {
        dfgFieldsLoadedHelper(false);
    }

    public void testDFGFieldsLoaded2() {
        dfgFieldsLoadedHelper(true);
    }

    public void dfgFieldsLoadedHelper(boolean related) {
        OpenJPAEntityManager em = (OpenJPAEntityManager) factory
            .createEntityManager();
        startTx(em);
        OpenJPAQuery q;
        Collection c;
        try {

            q = em.createQuery(
                "select a FROM " + CacheObjectA.class.getSimpleName()
                    + " a where a.name = :pName").setParameter("pName",
                ORIG_NAME);

            c = new ArrayList((Collection) q.getResultList());
            assertEquals(1, c.size());
            CacheObjectA a = (CacheObjectA) c.iterator().next();
            if (related)
                a.getRelatedArray();
            em.detach(a);
            assertEquals(ORIG_NAME, a.getName());
            q.closeAll();
        }
        finally {
            rollbackTx(em);
            endEm(em);
        }
    }

    // FIXME Seetha Sep 26,2006
    /*
      * public void testQueriesAfterModificationAreNotInCache() {
      * OpenJPAEntityManager em = (OpenJPAEntityManager)
      * factory.createEntityManager(); OpenJPAEntityManager em2 =
      * (OpenJPAEntityManager) factory.createEntityManager();
      *
      * //FIXME Seetha Sep 26,2006 //em.setIgnoreCache(false);
      * //em2.setIgnoreCache(false); ((FetchPlan) em.getFetchPlan()).
      * setFlushBeforeQueries(FetchPlan.FLUSH_TRUE); ((FetchPlan)
      * em2.getFetchPlan()). setFlushBeforeQueries(FetchPlan.FLUSH_TRUE);
      *
      * try { startTx(em); CacheObjectE e = new
      * CacheObjectE("e"); em.persist(e); endTx(em,
      * ());
      *
      * startTx(em);
      *  // first, a query that should get into the cache. Broker broker =
      * JPAFacadeHelper.toBroker(em); org.apache.openjpa.kernel.Query q =
      * broker.newQuery(JPQLParser.LANG_JPQL, CacheObjectE.class, "str ==
      * \"e\""); Collection c = (Collection) q.execute(); for (Iterator iter =
      * c.iterator(); iter.hasNext();) iter.next();
      *
      * assertEquals(1, c.size()); assertInCache(q, Boolean.TRUE);
      *
      * Broker broker2 = JPAFacadeHelper.toBroker(em2);
      * org.apache.openjpa.kernel.Query q2 = broker2.newQuery(q.getLanguage(),
      * q);
      *  // make some modifications and look again. Should return //two results.
      * e = new CacheObjectE("e"); em.persist(e);
      *
      * q = broker.newQuery(JPQLParser.LANG_JPQL, CacheObjectE.class, "str ==
      * \"e\""); c = (Collection) q.execute(); assertEquals(2, c.size()); for
      * (Iterator iter = c.iterator(); iter.hasNext();) iter.next();
      *  // original query should still be in cache assertInCache(q2,
      * Boolean.TRUE);
      *
      * Collection c2 = (Collection) q2.execute(); assertEquals(1, c2.size());
      *  // new query should not make it into cache
      *
      * q = broker .newQuery(JPQLParser.LANG_JPQL, CacheObjectE.class, null);
      * c = (Collection) q.execute(); assertEquals(2, c.size());
      * for (Iterator iter = c.iterator(); iter.hasNext();) iter.next();
      *
      * assertInCache(q, Boolean.FALSE); } finally {
      * rollbackTx(em);
      * endEm(em);
      * endEm(em2); } }
      */

    public void testCachedQueryClosureReleasesResources() {
        // PersistenceManagerFactory factory =
        // KodoHelper.createEntityManagerFactory ();
        EntityManager initEm = factory.createEntityManager();
        startTx(initEm);
        CacheObjectE e = new CacheObjectE("e");
        initEm.persist(e);
        endTx(initEm);
        endEm(initEm);

        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        org.apache.openjpa.kernel.Query q = broker.newQuery(
            JPQLParser.LANG_JPQL, "Select a FROM "
            + CacheObjectE.class.getSimpleName()
            + " a where a.str = 'e'");
        Collection c = (Collection) q.execute();
        for (Iterator iter = c.iterator(); iter.hasNext();)
            iter.next();

        assertEquals(1, c.size());
        assertInCache(q, Boolean.TRUE);

        ImplHelper.close(c);

        broker.close();
    }

    public void testMutableSCOsAreConverted() {
        OpenJPAEntityManager em0 = (OpenJPAEntityManager) factory
            .createEntityManager();
        OpenJPAEntityManager em1 = (OpenJPAEntityManager) factory
            .createEntityManager();

        startTx(em0);
        CacheObjectA a = (CacheObjectA) em0.find(CacheObjectA.class, oid);

        Date d = new Date();
        a.setDate(d);

        endTx(em0);
        DataCache cache = cacheManager(factory).getDataCache(
            DataCache.NAME_DEFAULT, false);
        assertTrue(cache.contains(oidwithclass));
        cache.remove(oidwithclass);

        a = (CacheObjectA) em1.find(CacheObjectA.class, oid);
        assertTrue(cache.contains(oidwithclass));

        try {
            PCData data = cache.get(oidwithclass);
            ClassMetaData meta =
                ((OpenJPAEntityManagerFactorySPI) OpenJPAPersistence
                    .cast(factory)).getConfiguration()
                    .getMetaDataRepositoryInstance().getMetaData(a.getClass(),
                    null, false);
            FieldMetaData fmd = meta.getField("date");
            d = (Date) data.getData(fmd.getIndex());
            Broker broker = JPAFacadeHelper.toBroker(em1);
            OpenJPAStateManager sm = broker.getStateManager(a);
            assertTrue(sm == ((ProxyDate) a.getDate()).getOwner());
            assertEquals(Date.class, d.getClass());
        }
        finally {
            endEm(em0);
            endEm(em1);
        }
    }

    public void testEmptyResultsAreCached() {
        Broker broker = JPAFacadeHelper.toBrokerFactory(factory).newBroker();
        org.apache.openjpa.kernel.Query q = broker.newQuery(
            JPQLParser.LANG_JPQL, "Select a FROM "
            + CacheObjectAChild1.class.getSimpleName()
            + " a where a.name = 'testEmptyResultsAreCached'");
        Collection c = (Collection) q.execute();
        assertEquals(0, c.size());
        assertInCache(q, Boolean.TRUE);
        broker.close();
    }

    private void doassertTrue(EntityManager em, String name, int age)
        throws Exception {
        CacheObjectA a = (CacheObjectA) em.find(CacheObjectA.class, oid);
        assertTrue(name.equals(a.getName()));
        assertTrue(a.getAge() == age);
        endEm(em);
    }

    private void assertNew(CacheObjectA a) {
        assertTrue(NEW_NAME.equals(a.getName()));
        assertTrue(ORIG_AGE == a.getAge());
    }

    private void assertOld(CacheObjectA a) {
        assertTrue(ORIG_NAME.equals(a.getName()));
        assertTrue(ORIG_AGE == a.getAge());
    }

    private DataCacheManager cacheManager(OpenJPAEntityManagerFactory factory) {
        return CacheTestHelper
            .cacheManager(JPAFacadeHelper.toBrokerFactory(factory));
    }

    private void close(EntityManager em) {
        rollbackTx(em);
        endEm(em);
    }

    private void close(Broker broker) {
        if (broker.isActive())
            broker.rollback();
        broker.close();
    }

    public static void main(String[] args) throws Exception {
        for (String type : args) {
            CacheTest c;
            if (type.equals("tcp")) {
                c = new DistributedCacheTest("time test",
                        ConcurrentDataCache.class);
            }
            else if (type.equals("jms")) {
                c = new DistributedCacheTest("time test",
                        ConcurrentDataCache.class);
            }
            else {
                c = new TestLocalCache("time test");
            }

            c.setUp();
            long start = System.currentTimeMillis();
            int count = 1000;
            for (int j = 0; j < count; j++) {
                c.doassertTrue(c.factory.createEntityManager(), NEW_NAME,
                        ORIG_AGE);
            }
            System.out.println(count + " iterations in "
                    + (System.currentTimeMillis() - start) + " millis");
            c.tearDown();
        }
    }
}
