/* | |
* 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.sql.Connection; | |
import java.sql.PreparedStatement; | |
import java.sql.SQLException; | |
import javax.persistence.EntityManager; | |
import javax.persistence.RollbackException; | |
import javax.persistence.LockModeType; | |
import javax.sql.DataSource; | |
import org.apache.openjpa.persistence.OpenJPAPersistence; | |
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; | |
import org.apache.openjpa.persistence.JPAFacadeHelper; | |
import org.apache.openjpa.persistence.test.SingleEMFTestCase; | |
import org.apache.openjpa.event.RemoteCommitListener; | |
import org.apache.openjpa.event.RemoteCommitEvent; | |
public class TestDataCacheOptimisticLockRecovery | |
extends SingleEMFTestCase { | |
private int pk; | |
private int remoteCommitEventStaleCount = 0; | |
private Object staleOid; | |
public void setUp() { | |
setUp("openjpa.DataCache", "true", | |
"openjpa.RemoteCommitProvider", "sjvm", | |
OptimisticLockInstance.class); | |
emf.getConfiguration().getRemoteCommitEventManager().addListener( | |
new RemoteCommitListener() { | |
public void afterCommit(RemoteCommitEvent e) { | |
if (e.getPayloadType() == | |
RemoteCommitEvent.PAYLOAD_LOCAL_STALE_DETECTION) { | |
remoteCommitEventStaleCount++; | |
staleOid = e.getUpdatedObjectIds().iterator().next(); | |
} | |
} | |
public void close() { | |
} | |
} | |
); | |
EntityManager em = emf.createEntityManager(); | |
em.getTransaction().begin(); | |
OptimisticLockInstance oli = new OptimisticLockInstance("foo"); | |
em.persist(oli); | |
em.getTransaction().commit(); | |
pk = oli.getPK(); | |
em.close(); | |
} | |
public void testOptimisticLockRecovery() | |
throws SQLException { | |
EntityManager em; | |
// 1. get the oplock value for the instance after commit and | |
// get a read lock to ensure that we check for the optimistic | |
// lock column at tx commit. | |
em = emf.createEntityManager(); | |
em.getTransaction().begin(); | |
OptimisticLockInstance oli = em.find(OptimisticLockInstance.class, pk); | |
Object oid = JPAFacadeHelper.toOpenJPAObjectId( | |
JPAFacadeHelper.getMetaData(oli), | |
OpenJPAPersistence.cast(em).getObjectId(oli)); | |
int firstOpLockValue = oli.getOpLock(); | |
em.lock(oli, LockModeType.READ); | |
// 2. make a change to the instance's optimistic lock column | |
// via direct SQL in a separate transaction | |
int secondOpLockValue = firstOpLockValue + 1; | |
OpenJPAEntityManagerFactorySPI emf = (OpenJPAEntityManagerFactorySPI) | |
OpenJPAPersistence.cast(em).getEntityManagerFactory(); | |
DataSource ds = (DataSource) emf.getConfiguration() | |
.getConnectionFactory(); | |
Connection c = ds.getConnection(); | |
c.setAutoCommit(false); | |
PreparedStatement ps = c.prepareStatement( | |
"UPDATE OPTIMISTIC_LOCK_INSTANCE SET OPLOCK = ? WHERE PK = ?"); | |
ps.setInt(1, secondOpLockValue); | |
ps.setInt(2, pk); | |
assertEquals(1, ps.executeUpdate()); | |
c.commit(); | |
// 3. commit the transaction, catching the expected oplock | |
// exception | |
try { | |
em.getTransaction().commit(); | |
fail("tx should have failed due to out-of-band oplock change"); | |
} catch (RollbackException re) { | |
// expected | |
} finally { | |
if (em.getTransaction().isActive()) | |
em.getTransaction().rollback(); | |
} | |
// 4. check that the corresponding remote commit event was fired | |
assertEquals(1, remoteCommitEventStaleCount); | |
assertEquals(oid, staleOid); | |
// 5. obtain the object in a new persistence context and | |
// assert that the oplock column is set to the one that | |
// happened in the out-of-band transaction | |
em.close(); | |
em = this.emf.createEntityManager(); | |
oli = em.find(OptimisticLockInstance.class, pk); | |
// If this fails, then the data cache has the wrong value. | |
// This is what this test case is designed to exercise. | |
assertEquals("data cache is not being cleared when oplock " | |
+ "violations occur", secondOpLockValue, oli.getOpLock()); | |
// 6. get a read lock on the instance and commit the tx; this | |
// time it should go through | |
em.getTransaction().begin(); | |
em.lock(oli, LockModeType.READ); | |
try { | |
em.getTransaction().commit(); | |
} catch (RollbackException e) { | |
throw e; | |
} finally { | |
if (em.getTransaction().isActive()) | |
em.getTransaction().rollback(); | |
} | |
em.close(); | |
} | |
public void testExpectedOptimisticLockException() { | |
EntityManager em; | |
// 1. start a new tx | |
em = emf.createEntityManager(); | |
em.getTransaction().begin(); | |
em.lock(em.find(OptimisticLockInstance.class, pk), LockModeType.READ); | |
// 2. start another tx, and cause a version increment | |
EntityManager em2 = emf.createEntityManager(); | |
em2.getTransaction().begin(); | |
em2.lock(em2.find(OptimisticLockInstance.class, pk), | |
LockModeType.WRITE); | |
em2.getTransaction().commit(); | |
em2.close(); | |
// 3. try to commit. this should fail, as this is a regular optimistic | |
// lock failure situation. | |
try { | |
em.getTransaction().commit(); | |
fail("write lock in em2 should trigger an optimistic lock failure"); | |
} catch (RollbackException pe) { | |
// expected | |
} | |
em.close(); | |
} | |
} |