/*
 * 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();
