package org.hibernate.test.cache.infinispan.functional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.persistence.OptimisticLockException;
import javax.persistence.PessimisticLockException;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.infinispan.InfinispanRegionFactory;
import org.hibernate.cache.infinispan.entity.EntityRegionImpl;
import org.hibernate.cache.infinispan.util.Caches;
import org.hibernate.cache.infinispan.util.InfinispanMessageLogger;
import org.hibernate.cache.spi.Region;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.AfterClassOnce;
import org.hibernate.testing.BeforeClassOnce;
import org.hibernate.test.cache.infinispan.functional.entities.Item;
import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory;
import org.hibernate.test.cache.infinispan.util.TestTimeService;
import org.junit.After;
import org.junit.Before;
import org.infinispan.AdvancedCache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Common base for TombstoneTest and VersionedTest
*
* @author Radim Vansa <rvansa@redhat.com>
*/
public abstract class AbstractNonInvalidationTest extends SingleNodeTest {
protected static final int WAIT_TIMEOUT = 2000;
protected static final TestTimeService TIME_SERVICE = new TestTimeService();
protected long TIMEOUT;
protected ExecutorService executor;
protected InfinispanMessageLogger log = InfinispanMessageLogger.Provider.getLog(getClass());
protected AdvancedCache entityCache;
protected long itemId;
protected Region region;
protected long timeout;
protected final List<Runnable> cleanup = new ArrayList<>();
@BeforeClassOnce
public void setup() {
executor = Executors.newCachedThreadPool(new ThreadFactory() {
AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Executor-" + counter.incrementAndGet());
}
});
}
@AfterClassOnce
public void shutdown() {
executor.shutdown();
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
// This applies to manually set LOCK_TIMEOUT for H2 DB. AvailableSettings.JPA_LOCK_TIMEOUT
// works only for queries, not for CRUDs, so we have to modify the connection URL.
// Alternative could be executing SET LOCK_TIMEOUT 100 as a native query.
String url = (String) ssrb.getSettings().get(AvailableSettings.URL);
if (url != null && url.contains("LOCK_TIMEOUT")) {
url = url.replaceAll("LOCK_TIMEOUT=[^;]*", "LOCK_TIMEOUT=100");
}
ssrb.applySetting(AvailableSettings.URL, url);
}
@Override
protected void startUp() {
super.startUp();
InfinispanRegionFactory regionFactory = (InfinispanRegionFactory) sessionFactory().getSettings().getRegionFactory();
TIMEOUT = regionFactory.getPendingPutsCacheConfiguration().expiration().maxIdle();
region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName());
entityCache = ((EntityRegionImpl) region).getCache();
}
@Before
public void insertAndClearCache() throws Exception {
region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName());
entityCache = ((EntityRegionImpl) region).getCache();
timeout = ((EntityRegionImpl) region).getRegionFactory().getPendingPutsCacheConfiguration().expiration().maxIdle();
Item item = new Item("my item", "Original item");
withTxSession(s -> s.persist(item));
entityCache.clear();
assertEquals("Cache is not empty", Collections.EMPTY_SET, entityCache.keySet());
itemId = item.getId();
log.info("Insert and clear finished");
}
@After
public void cleanup() throws Exception {
cleanup.forEach(Runnable::run);
cleanup.clear();
withTxSession(s -> {
s.createQuery("delete from Item").executeUpdate();
});
}
protected Future<Boolean> removeFlushWait(long id, CyclicBarrier loadBarrier, CountDownLatch preFlushLatch, CountDownLatch flushLatch, CountDownLatch commitLatch) throws Exception {
return executor.submit(() -> withTxSessionApply(s -> {
try {
Item item = s.load(Item.class, id);
item.getName(); // force load & putFromLoad before the barrier
loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS);
s.delete(item);
if (preFlushLatch != null) {
awaitOrThrow(preFlushLatch);
}
s.flush();
} catch (OptimisticLockException e) {
log.info("Exception thrown: ", e);
markRollbackOnly(s);
return false;
} catch (PessimisticLockException e) {
log.info("Exception thrown: ", e);
markRollbackOnly(s);
return false;
} finally {
if (flushLatch != null) {
flushLatch.countDown();
}
}
awaitOrThrow(commitLatch);
return true;
}));
}
protected Future<Boolean> updateFlushWait(long id, CyclicBarrier loadBarrier, CountDownLatch preFlushLatch, CountDownLatch flushLatch, CountDownLatch commitLatch) throws Exception {
return executor.submit(() -> withTxSessionApply(s -> {
try {
Item item = s.load(Item.class, id);
item.getName(); // force load & putFromLoad before the barrier
if (loadBarrier != null) {
loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS);
}
item.setDescription("Updated item");
s.update(item);
if (preFlushLatch != null) {
awaitOrThrow(preFlushLatch);
}
s.flush();
} catch (OptimisticLockException e) {
log.info("Exception thrown: ", e);
markRollbackOnly(s);
return false;
} catch (PessimisticLockException | org.hibernate.PessimisticLockException e) {
log.info("Exception thrown: ", e);
markRollbackOnly(s);
return false;
} finally {
if (flushLatch != null) {
flushLatch.countDown();
}
}
if (commitLatch != null) {
awaitOrThrow(commitLatch);
}
return true;
}));
}
protected Future<Boolean> evictWait(long id, CyclicBarrier loadBarrier, CountDownLatch preEvictLatch, CountDownLatch postEvictLatch) throws Exception {
return executor.submit(() -> {
try {
loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS);
if (preEvictLatch != null) {
awaitOrThrow(preEvictLatch);
}
sessionFactory().getCache().evictEntity(Item.class, id);
} finally {
if (postEvictLatch != null) {
postEvictLatch.countDown();
}
}
return true;
});
}
protected void awaitOrThrow(CountDownLatch latch) throws InterruptedException, TimeoutException {
if (!latch.await(WAIT_TIMEOUT, TimeUnit.SECONDS)) {
throw new TimeoutException();
}
}
@Override
protected void addSettings(Map settings) {
super.addSettings(settings);
settings.put(TestInfinispanRegionFactory.TIME_SERVICE, TIME_SERVICE);
}
protected void assertEmptyCache() {
assertNull(entityCache.get(itemId)); // force expiration
Map contents = Caches.entrySet(entityCache).toMap();
assertEquals(Collections.EMPTY_MAP, contents);
}
protected <T> T assertCacheContains(Class<T> expected) {
Map contents = Caches.entrySet(entityCache).toMap();
assertEquals("Cache does not have single element", 1, contents.size());
Object value = contents.get(itemId);
assertTrue(String.valueOf(value), expected.isInstance(value));
return (T) value;
}
protected Object assertSingleCacheEntry() {
return assertCacheContains(CacheEntry.class);
}
}