From 534778acf8a25814e153af4ea88d636b011ab2c0 Mon Sep 17 00:00:00 2001 From: Pinaki Poddar Date: Fri, 23 May 2008 21:35:32 +0000 Subject: [PATCH] OPENJPA-610 Test cases to identify use cases where behavior differs because of DataCache git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@659669 13f79535-47bb-0310-9956-ffa450edef68 --- .../apps/BidirectionalOne2OneOwned.java | 89 ++++ .../apps/BidirectionalOne2OneOwner.java | 86 ++++ .../apps/UnidirectionalOne2OneOwned.java | 77 +++ .../apps/UnidirectionalOne2OneOwner.java | 87 ++++ .../TestDataCacheBehavesIdentical.java | 444 ++++++++++++++++++ 5 files changed, 783 insertions(+) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwned.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwner.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwned.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwner.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwned.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwned.java new file mode 100644 index 000000000..58fad6365 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwned.java @@ -0,0 +1,89 @@ +package org.apache.openjpa.persistence.cache.common.apps; + +import javax.persistence.*; +/* + * 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. + */ + +/** + * A persistent entity that is owned by unidirectional single-valued + * relationship. + * A unidirectional relationship has only one owning side and the other side + * called as owned side is this receiver. + * Given the following relationship between Entity A and Entity B: + * Entity A refers a single instance of Entity B + * Entity B does not refer Entity A (owner) + * Entity A is called owner and Entity B is called owned with respect + * to the above relationship. + * + * Used to test identical application behavior with or without DataCache. + * + * @see BidirectionalOne2OneOwned + * @see TestDataCacheBehavesIdentical + * @see Section 2.1.8.3 of JPA Specification Version 1.0 + * + * @author Pinaki Poddar + * + */ + +@Entity +public class BidirectionalOne2OneOwned { + @Id + private long id; + + private String name; + + @OneToOne(mappedBy="owned") + private BidirectionalOne2OneOwner owner; + + @Version + private int version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BidirectionalOne2OneOwner getOwner() { + return owner; + } + + public void setOwner(BidirectionalOne2OneOwner owner) { + this.owner = owner; + } + + public int getVersion() { + return version; + } + + public String toString() { + return this.getClass().getSimpleName() + ":" + id + ":" + name; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwner.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwner.java new file mode 100644 index 000000000..6c0c9a974 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/BidirectionalOne2OneOwner.java @@ -0,0 +1,86 @@ +/* + * 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.cache.common.apps; + +import javax.persistence.*; + +/** + * A persistent entity that owns bidirectional single-valued relationship. + * A bidirectional relationship has only an owning side, which is this receiver. + * Given the following relationship between Entity A and Entity B: + * Entity A refers to a single instance of Entity B + * Entity B refers to a single instance of Entity A + * If Entity B qualifies its relation to the Entity A with mappedBy + * annotation qualifier then Entity B is called owned and Entity A is called + * owner with respect to the above relationship. + * + * Used to test identical application behavior with or without DataCache. + * + * @see BidirectionalOne2OneOwned + * + * @author Pinaki Poddar + * + */ +@Entity +public class BidirectionalOne2OneOwner { + @Id + private long id; + + private String name; + + @OneToOne + private BidirectionalOne2OneOwned owned; + + @Version + private int version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BidirectionalOne2OneOwned getOwned() { + return owned; + } + + public void setOwned(BidirectionalOne2OneOwned owned) { + this.owned = owned; + } + + public int getVersion() { + return version; + } + + public String toString() { + return this.getClass().getSimpleName() + ":" + id + ":" + name; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwned.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwned.java new file mode 100644 index 000000000..35243e180 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwned.java @@ -0,0 +1,77 @@ +/* + * 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.cache.common.apps; + +import javax.persistence.*; +/** + * A persistent entity that is owned by unidirectional single-valued + * relationship. + * A unidirectional relationship has only one owning side and the other side + * called as owned side is this receiver. + * Given the following relationship between Entity A and Entity B: + * Entity A refers a single instance of Entity B + * Entity B does not refer Entity A (owner) + * Entity A is called owner and Entity B is called owned with respect + * to the above relationship. + * + * Used to test identical application behavior with or without DataCache. + * + * @see UnidirectionalOne2OneOwned + * @see TestDataCacheBehavesIdentical + * @see Section 2.1.8.3 of JPA Specification Version 1.0 + * + * @author Pinaki Poddar + * + */ + +@Entity +public class UnidirectionalOne2OneOwned { + @Id + private long id; + + private String name; + + @Version + private int version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getVersion() { + return version; + } + + public String toString() { + return this.getClass().getSimpleName() + ":" + id + ":" + name; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwner.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwner.java new file mode 100644 index 000000000..84922b101 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/cache/common/apps/UnidirectionalOne2OneOwner.java @@ -0,0 +1,87 @@ +/* + * 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.cache.common.apps; + +import javax.persistence.*; + +/** + * A persistent entity that owns unidirectional single-valued relationship. + * A unidirectional relationship has only one owning side, which is this + * receiver. + * Given the following relationship between Entity A and Entity B: + * Entity A refers a single instance of Entity B + * Entity B does not refer Entity A (owner) + * Entity A is called owner and Entity B is called owned with respect + * to the above relationship. + * + * Used to test identical application behavior with or without DataCache. + * + * @see UnidirectionalOne2OneOwned + * @see TestDataCacheBehavesIdentical + * @see Section 2.1.8.3 of JPA Specification Version 1.0 + * + * @author Pinaki Poddar + * + */ +@Entity +public class UnidirectionalOne2OneOwner { + @Id + private long id; + + private String name; + + @OneToOne + private UnidirectionalOne2OneOwned owned; + + @Version + private int version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public UnidirectionalOne2OneOwned getOwned() { + return owned; + } + + public void setOwned(UnidirectionalOne2OneOwned owned) { + this.owned = owned; + } + + public int getVersion() { + return version; + } + + public String toString() { + return this.getClass().getSimpleName() + ":" + id + ":" + name; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java new file mode 100644 index 000000000..56c800036 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java @@ -0,0 +1,444 @@ +/* + * 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 javax.persistence.EntityManager; +import javax.persistence.EntityNotFoundException; +import javax.persistence.LockModeType; + +import org.apache.openjpa.persistence.EntityManagerImpl; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; +import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI; +import org.apache.openjpa.persistence.StoreCache; +import org.apache.openjpa.persistence.StoreCacheImpl; +import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwned; +import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwner; +import org.apache.openjpa.persistence.common.utils.AbstractTestCase; +import org.apache.openjpa.persistence.exception.PObject; + +/** + * Tests various application behavior with or without DataCache. + * Ideally, an application should behave identically irrespective of the + * DataCache. However, purpose of this test is to identify specific scenarios + * where this ideal is violated. The test case also demonstrates, wherever + * possible, what extra step an application may take to ensure that its + * behavior with or without DataCache remains identical. + * + * So far following use cases are found to demonstrate behavioral differences: + * 1. Inconsistent bidirectional relation + * 2. Refresh + * + * @author Pinaki Poddar + * + */ +public class TestDataCacheBehavesIdentical extends AbstractTestCase { + private static OpenJPAEntityManagerFactorySPI emfWithDataCache; + private static OpenJPAEntityManagerFactorySPI emfWithoutDataCache; + + private static final boolean WITH_DATACACHE = true; + private static final boolean CONSISTENT = true; + private static final boolean DIRTY = true; + private static final LockModeType NOLOCK = null; + + private static final String MARKER_DATACACHE = "in DataCache"; + private static final String MARKER_DATABASE = "in Database"; + private static final String MARKER_CACHE = "in Object Cache"; + private static final String MARKER_DIRTY_CACHE = "in Object Cache (dirty)"; + private static long ID_COUNTER = System.currentTimeMillis(); + + + /** + * Sets up two EntityManagerFactory: one with DataCache another without. + */ + public void setUp() throws Exception { + super.setUp(); + if (emfWithDataCache == null) { + emfWithDataCache = createEMF( + "openjpa.jdbc.SynchronizeMappings", "buildSchema", + "openjpa.RuntimeUnenhancedClasses", "unsupported", + "openjpa.DataCache", "true", + "openjpa.RemoteCommitProvider", "sjvm", + "openjpa.jdbc.UpdateManager", "constraint", + PObject.class, + BidirectionalOne2OneOwner.class, + BidirectionalOne2OneOwned.class, CLEAR_TABLES); + emfWithoutDataCache = createEMF( + "openjpa.RuntimeUnenhancedClasses", "unsupported", + "openjpa.DataCache", "false", + "openjpa.jdbc.UpdateManager", "constraint", + PObject.class, + BidirectionalOne2OneOwned.class, + BidirectionalOne2OneOwner.class, CLEAR_TABLES); + + assertNotNull(emfWithDataCache); + assertNotNull(emfWithoutDataCache); + + // StoreCache is, by design, always non-null + assertNotNull(emfWithDataCache.getStoreCache()); + assertNotNull(emfWithoutDataCache.getStoreCache()); + + // however, following distinguishes whether DataCache is active + assertTrue(isDataCacheActive(emfWithDataCache)); + assertFalse(isDataCacheActive(emfWithoutDataCache)); + } + } + + /** + * Affirms via internal structures if the given factory is configured with + * active DataCache. Because, even when DataCache is configured to be + * false, a no-op StoreCache is instantiated by design. + */ + boolean isDataCacheActive(OpenJPAEntityManagerFactorySPI emf) { + return ((StoreCacheImpl) emf.getStoreCache()).getDelegate() != null + && emf.getConfiguration() + .getDataCacheManagerInstance() + .getSystemDataCache() != null; + } + + /** + * Create one-to-one bidirectional relation (may or may not be consistent) + * between two pairs of instances. Creates four instances Owner1, Owned1, + * Owner2, Owned2. The first instance has the given id. The id of the other + * instances monotonically increase by 1. The relationship is set either + * consistently or inconsistently. Consistent relation is when Owner1 points + * to Owned1 and Owned1 points back to Owner1. Inconsistent relation is when + * Owner1 points to Owned1 but Owned1 points to Owner2 instead of Owner1. + * + * + * @param em + * the entity manager to persist the instances + * @param id + * the identifier of the first owner instance. The identifier for + * the other instances are sequential in order of creation. + * @param consistent + * if true sets the relationship as consistent. + */ + public void createBidirectionalRelation(EntityManager em, long id, + boolean consistent) { + BidirectionalOne2OneOwner owner1 = new BidirectionalOne2OneOwner(); + BidirectionalOne2OneOwned owned1 = new BidirectionalOne2OneOwned(); + BidirectionalOne2OneOwner owner2 = new BidirectionalOne2OneOwner(); + BidirectionalOne2OneOwned owned2 = new BidirectionalOne2OneOwned(); + + owner1.setId(id++); + owned1.setId(id++); + owner2.setId(id++); + owned2.setId(id++); + + owner1.setName("Owner1"); + owned1.setName("Owned1"); + owned2.setName("Owned2"); + owner2.setName("Owner2"); + + owner1.setOwned(owned1); + owner2.setOwned(owned2); + + if (consistent) { + owned1.setOwner(owner1); + owned2.setOwner(owner2); + } else { + owned1.setOwner(owner2); + owned2.setOwner(owner1); + } + + em.getTransaction().begin(); + em.persist(owner1); + em.persist(owned1); + em.persist(owner2); + em.persist(owned2); + em.getTransaction().commit(); + em.clear(); + } + + /** + * Verifies that bidirectionally related objects can be persisted + * and later retrieved in a different transaction. + * + * Creates interrelated set of four instances. + * Establish their relation either consistently or inconsistently based + * on the given flag. + * Persist them and then clear the context. + * Fetch the instances in memory again by their identifiers. + * Compare the interrelations between the fetched instances with the + * relations of the original instances (which can be consistent or + * inconsistent). + * + * The mapping specification is such that the bidirectional relation is + * stored in database by a single foreign key. Hence database relation + * is always consistent. Hence the instances retrieved from database are + * always consistently related irrespective of whether they were created + * with consistent or inconsistent relation. + * However, when the instances are retrieved from the data cache, data cache + * will preserve the in-memory relations even when they are inconsistent. + * + * @param useDataCache + * use DataCache + * @param consistent + * assume that the relationship were created as consistent. + */ + public void verifyBidirectionalRelation(boolean useDataCache, + boolean createConsistent, boolean expectConsistent) { + EntityManager em = (useDataCache) + ? emfWithDataCache.createEntityManager() + : emfWithoutDataCache.createEntityManager(); + + long id = ID_COUNTER++; + ID_COUNTER += 4; + createBidirectionalRelation(em, id, createConsistent); + + + BidirectionalOne2OneOwner owner1 = em.find(BidirectionalOne2OneOwner.class, id); + BidirectionalOne2OneOwned owned1 = em.find(BidirectionalOne2OneOwned.class, id + 1); + BidirectionalOne2OneOwner owner2 = em.find(BidirectionalOne2OneOwner.class, id + 2); + BidirectionalOne2OneOwned owned2 = em.find(BidirectionalOne2OneOwned.class, id + 3); + + assertNotNull(owner1); + assertNotNull(owner2); + assertNotNull(owned1); + assertNotNull(owned2); + + assertEquals(owner1, expectConsistent + ? owner1.getOwned().getOwner() + : owner2.getOwned().getOwner()); + assertEquals(owner2, expectConsistent + ? owner2.getOwned().getOwner() + : owner1.getOwned().getOwner()); + + + assertEquals(owned1, owner1.getOwned()); + assertEquals(expectConsistent ? owner1 : owner2, owned1.getOwner()); + assertEquals(owned2, owner2.getOwned()); + assertEquals(expectConsistent ? owner2 : owner1, owned2.getOwner()); + } + + public void testConsitentBidirectionalRelationIsPreservedWithDataCache() { + verifyBidirectionalRelation(WITH_DATACACHE, CONSISTENT, CONSISTENT); + } + + public void testConsitentBidirectionalRelationIsPreservedWithoutDataCache() { + verifyBidirectionalRelation(!WITH_DATACACHE, CONSISTENT, CONSISTENT); + } + + public void testInconsitentBidirectionalRelationIsPreservedWithDataCache() { + verifyBidirectionalRelation(WITH_DATACACHE, !CONSISTENT, !CONSISTENT); + } + + public void testInconsitentBidirectionalRelationIsNotPreservedWithoutDataCache() { + verifyBidirectionalRelation(!WITH_DATACACHE, !CONSISTENT, CONSISTENT); + } + + /** + * Verify that refresh() may fetch state from either the data cache or the + * database based on different conditions. + * The conditions that impact are + * a) whether current lock is stronger than NONE + * b) whether the instance being refreshed is dirty + * + * An instance is created with data cache marker and persisted. + * A native SQL is used to update the database record with database marker. + * The in-memory instance is not aware of this out-of-band update. + * Then the in-memory instance is refreshed. The marker of the refreshed + * instance tells whether the instance is refreshed from the data cache + * of the database. + * + * @param useDataCache flags if data cache is active. if not, then surely + * refresh always fetch state from the database. + * + * @param datacache the marker for the copy of the data cached instance + * @param database the marker for the database record + * @param lock lock to be used + */ + public void verifyRefresh(boolean useDataCache, LockModeType lock, boolean makeDirtyBeforeRefresh) { + OpenJPAEntityManagerFactorySPI emf = (useDataCache) + ? emfWithDataCache : emfWithoutDataCache; + + OpenJPAEntityManagerSPI em = emf.createEntityManager(); + + em.getTransaction().begin(); + PObject pc = new PObject(); + pc.setName(useDataCache ? MARKER_DATACACHE : MARKER_CACHE); + em.persist(pc); + em.getTransaction().commit(); + + Object oid = pc.getId(); + StoreCache dataCache = emf.getStoreCache(); + assertEquals(useDataCache, dataCache.contains(PObject.class, oid)); + + // Modify the record in the database in a separate transaction using + // native SQL so that the in-memory instance is not altered + em.getTransaction().begin(); + String sql = "UPDATE POBJECT SET NAME='"+ MARKER_DATABASE +"' WHERE id="+oid; + em.createNativeQuery(sql).executeUpdate(); + em.getTransaction().commit(); + + assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName()); + + em.getTransaction().begin(); + if (makeDirtyBeforeRefresh) { + pc.setName(MARKER_DIRTY_CACHE); + } + assertEquals(makeDirtyBeforeRefresh, em.isDirty(pc)); + + if (lock != null) { + ((EntityManagerImpl)em).getFetchPlan().setReadLockMode(lock); + } + em.refresh(pc); + + String expected = getExpectedMarker(useDataCache, lock, makeDirtyBeforeRefresh); + assertEquals(expected, pc.getName()); + em.getTransaction().commit(); + } + + /** + * The expected marker i.e. where the state is refreshed from depends on + * a) whether DataCache is active + * b) whether current Lock is stronger than NOLOCK + * c) whether the object to be refreshed is dirty + * + * The following truth table enumerates the possibilities + * + * Use Cache? Lock? Dirty? Target + * Y Y Y Database + * Y N Y Data Cache + * Y Y N Data Cache + * Y N N Data Cache + * + * N Y Y Database + * N N Y Database + * N Y N Object Cache + * N N N Object Cache + * + * @param datacache the marker for + * @param database + * @param useDataCache + * @param lock + * @param makeDirtyBeforeRefresh + * @return + */ + String getExpectedMarker(boolean useDataCache, LockModeType lock, boolean makeDirtyBeforeRefresh) { + if (useDataCache) { + return (lock != null && makeDirtyBeforeRefresh) + ? MARKER_DATABASE : MARKER_DATACACHE; + } else { + return (makeDirtyBeforeRefresh) ? MARKER_DATABASE : MARKER_CACHE; + } + } + + public void testDirtyRefreshWithNoLockHitsDataCache() { + verifyRefresh(WITH_DATACACHE, NOLOCK, DIRTY); + } + + public void testCleanRefreshWithNoLockHitsDataCache() { + verifyRefresh(WITH_DATACACHE, NOLOCK, !DIRTY); + } + + public void testDirtyRefreshWithReadLockHitsDatabase() { + verifyRefresh(WITH_DATACACHE, LockModeType.READ, DIRTY); + } + + public void testCleanRefreshWithReadLockHitsDataCache() { + verifyRefresh(WITH_DATACACHE, LockModeType.READ, !DIRTY); + } + + public void testDirtyRefreshWithWriteLockHitsDatabase() { + verifyRefresh(WITH_DATACACHE, LockModeType.WRITE, DIRTY); + } + + public void testCleanRefreshWithWriteLockHitsDatabase() { + verifyRefresh(WITH_DATACACHE, LockModeType.WRITE, !DIRTY); + } + + public void testDirtyRefreshWithoutDataCacheAlwaysHitsDatabase() { + verifyRefresh(!WITH_DATACACHE, NOLOCK, DIRTY); + verifyRefresh(!WITH_DATACACHE, LockModeType.READ, DIRTY); + verifyRefresh(!WITH_DATACACHE, LockModeType.WRITE, DIRTY); + } + + public void testCleanRefreshWithoutDataCacheNeverHitsDatabase() { + verifyRefresh(!WITH_DATACACHE, NOLOCK, !DIRTY); + verifyRefresh(!WITH_DATACACHE, LockModeType.READ, !DIRTY); + verifyRefresh(!WITH_DATACACHE, LockModeType.WRITE, !DIRTY); + } + + + public void verifyDeleteDetectionOnRefresh(boolean useDataCache, LockModeType lock) { + OpenJPAEntityManagerFactorySPI emf = (useDataCache) + ? emfWithDataCache : emfWithoutDataCache; + + OpenJPAEntityManagerSPI em = emf.createEntityManager(); + + em.getTransaction().begin(); + PObject pc = new PObject(); + pc.setName(useDataCache ? MARKER_DATACACHE : MARKER_CACHE); + em.persist(pc); + em.getTransaction().commit(); + + Object oid = pc.getId(); + StoreCache dataCache = emf.getStoreCache(); + assertEquals(useDataCache, dataCache.contains(PObject.class, oid)); + + // delete the record in the database in a separate transaction using + // native SQL so that the in-memory instance is not altered + em.getTransaction().begin(); + String sql = "DELETE FROM POBJECT WHERE id="+oid; + em.createNativeQuery(sql).executeUpdate(); + em.getTransaction().commit(); + + // the object cache does not know that the record was deleted + assertTrue(em.contains(pc)); + // nor does the data cache + assertEquals(useDataCache, dataCache.contains(PObject.class, oid)); + + assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName()); + + em.getTransaction().begin(); + if (lock != null) + em.getFetchPlan().setReadLockMode(lock); + try { + em.refresh(pc); + if (lock == null) { + assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName()); + } else { + fail("expected EntityNotFoundException for PObject:" + oid); + } + } catch (EntityNotFoundException ex) { + if (lock != null) { + // we are good + } + } finally { + em.getTransaction().rollback(); + } + } + + public void testDeleteIsDetectedOnRefreshWithLockWithActiveDataCache() { + verifyDeleteDetectionOnRefresh(WITH_DATACACHE, LockModeType.READ); + verifyDeleteDetectionOnRefresh(WITH_DATACACHE, LockModeType.WRITE); + } + + public void testDeleteIsNotDetectedOnRefreshWithNoLockWithActiveDataCache() { + verifyDeleteDetectionOnRefresh(WITH_DATACACHE, NOLOCK); + } + + public void testDeleteIsDetectedOnRefreshAlwaysWithoutDataCache() { + verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, NOLOCK); + verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, LockModeType.READ); + verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, LockModeType.WRITE); + } + +}