Index: core/src/main/java/org/infinispan/manager/DefaultCacheManager.java =================================================================== --- core/src/main/java/org/infinispan/manager/DefaultCacheManager.java (revision 2355) +++ core/src/main/java/org/infinispan/manager/DefaultCacheManager.java (revision ) @@ -412,6 +412,7 @@ */ @SuppressWarnings("unchecked") public Cache getCache(String cacheName) { + assertIsNotTerminated(); if (cacheName == null) throw new NullPointerException("Null arguments not allowed"); @@ -436,6 +437,11 @@ } } + private void assertIsNotTerminated() { + if (globalComponentRegistry.getStatus() == ComponentStatus.TERMINATED) + throw new IllegalStateException("Cache container has been stopped and cannot be reused. Recreate the cache container."); + } + public String getClusterName() { return globalConfiguration.getClusterName(); } @@ -461,8 +467,11 @@ @GuardedBy("Cache name lock container keeps a lock per cache name which guards this method") private Cache createCache(String cacheName) { Cache existingCache = caches.get(cacheName); - if (existingCache != null) + if (existingCache != null) { + if (existingCache.getStatus() == ComponentStatus.TERMINATED) + existingCache.start(); return existingCache; + } Configuration c; if (cacheName.equals(DEFAULT_CACHE_NAME) || !configurationOverrides.containsKey(cacheName)) Index: core/src/main/java/org/infinispan/CacheDelegate.java =================================================================== --- core/src/main/java/org/infinispan/CacheDelegate.java (revision 2326) +++ core/src/main/java/org/infinispan/CacheDelegate.java (revision ) @@ -164,7 +164,17 @@ if (key == null) throw new NullPointerException("Null keys are not supported!"); } + private void restartIfTerminated() { + if (componentRegistry.getStatus() == ComponentStatus.TERMINATED) { + if (cacheManager.getStatus() != ComponentStatus.TERMINATED) + start(); + else + throw new IllegalStateException("Cache container has been stopped and its caches are not usable. Recreate the cache container and retrieve the cache."); + } + } + public final boolean remove(Object key, Object value) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); RemoveCommand command = commandsFactory.buildRemoveCommand(key, value); @@ -172,15 +182,18 @@ } public final int size() { + restartIfTerminated(); SizeCommand command = commandsFactory.buildSizeCommand(); return (Integer) invoker.invoke(icc.createNonTxInvocationContext(), command); } public final boolean isEmpty() { + restartIfTerminated(); return size() == 0; } public final boolean containsKey(Object key) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key); @@ -194,6 +207,7 @@ @SuppressWarnings("unchecked") public final V get(Object key) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key); @@ -202,6 +216,7 @@ @SuppressWarnings("unchecked") public final V remove(Object key) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); RemoveCommand command = commandsFactory.buildRemoveCommand(key, null); @@ -209,6 +224,7 @@ } public final void clear() { + restartIfTerminated(); InvocationContext ctx = getInvocationContext(false); ClearCommand command = commandsFactory.buildClearCommand(); invoker.invoke(ctx, command); @@ -216,23 +232,27 @@ @SuppressWarnings("unchecked") public Set keySet() { + restartIfTerminated(); KeySetCommand command = commandsFactory.buildKeySetCommand(); return (Set) invoker.invoke(icc.createNonTxInvocationContext(), command); } @SuppressWarnings("unchecked") public Collection values() { + restartIfTerminated(); ValuesCommand command = commandsFactory.buildValuesCommand(); return (Collection) invoker.invoke(icc.createNonTxInvocationContext(), command); } @SuppressWarnings("unchecked") public Set> entrySet() { + restartIfTerminated(); EntrySetCommand command = commandsFactory.buildEntrySetCommand(); return (Set>) invoker.invoke(icc.createNonTxInvocationContext(), command); } public final void putForExternalRead(K key, V value) { + restartIfTerminated(); Transaction ongoingTransaction = null; try { if (transactionManager != null && (ongoingTransaction = transactionManager.getTransaction()) != null) { @@ -255,6 +275,7 @@ } public final void evict(K key) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(true); EvictCommand command = commandsFactory.buildEvictCommand(key); @@ -286,11 +307,13 @@ } public void lock(K key) { + restartIfTerminated(); assertKeyNotNull(key); lock(Collections.singletonList(key)); } public void lock(Collection keys) { + restartIfTerminated(); if (keys == null || keys.isEmpty()) throw new IllegalArgumentException("Cannot lock empty list of keys"); LockControlCommand command = commandsFactory.buildLockControlCommand(keys, false); @@ -421,6 +444,7 @@ @SuppressWarnings("unchecked") public final V put(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); PutKeyValueCommand command = commandsFactory.buildPutKeyValueCommand(key, value, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime)); @@ -429,6 +453,7 @@ @SuppressWarnings("unchecked") public final V putIfAbsent(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext context = getInvocationContext(false); PutKeyValueCommand command = commandsFactory.buildPutKeyValueCommand(key, value, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime)); @@ -437,6 +462,7 @@ } public final void putAll(Map map, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit) { + restartIfTerminated(); assertKeysNotNull(map); PutMapCommand command = commandsFactory.buildPutMapCommand(map, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime)); invoker.invoke(getInvocationContext(false), command); @@ -444,6 +470,7 @@ @SuppressWarnings("unchecked") public final V replace(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ReplaceCommand command = commandsFactory.buildReplaceCommand(key, null, value, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime)); @@ -452,6 +479,7 @@ } public final boolean replace(K key, V oldValue, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ReplaceCommand command = commandsFactory.buildReplaceCommand(key, oldValue, value, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime)); @@ -485,6 +513,7 @@ } public final NotifyingFuture putAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -493,6 +522,7 @@ } public final NotifyingFuture putAllAsync(Map data, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) { + restartIfTerminated(); assertKeysNotNull(data); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -501,6 +531,7 @@ } public final NotifyingFuture clearAsync() { + restartIfTerminated(); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); ClearCommand command = commandsFactory.buildClearCommand(); @@ -508,6 +539,7 @@ } public final NotifyingFuture putIfAbsentAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -517,6 +549,7 @@ } public final NotifyingFuture removeAsync(Object key) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -525,6 +558,7 @@ } public final NotifyingFuture removeAsync(Object key, Object value) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -533,6 +567,7 @@ } public final NotifyingFuture replaceAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); @@ -541,6 +576,7 @@ } public final NotifyingFuture replaceAsync(K key, V oldValue, V newValue, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) { + restartIfTerminated(); assertKeyNotNull(key); InvocationContext ctx = getInvocationContext(false); ctx.setUseFutureReturnType(true); Index: core/src/test/java/org/infinispan/api/CacheRestartTest.java =================================================================== --- core/src/test/java/org/infinispan/api/CacheRestartTest.java (revision ) +++ core/src/test/java/org/infinispan/api/CacheRestartTest.java (revision ) @@ -0,0 +1,74 @@ +package org.infinispan.api; + +import org.infinispan.Cache; +import org.infinispan.lifecycle.ComponentStatus; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.test.SingleCacheManagerTest; +import org.infinispan.test.TestingUtil; +import org.infinispan.test.fwk.TestCacheManagerFactory; +import org.testng.annotations.Test; + +/** + * Test that verifies the behaivour of Cache and CacheContainer.getCache() calls after + * Cache and CacheContainer instances have been stopped. This emulates redeployment + * scenarios under a situations where the CacheContainer is a shared resource. + * + * @author Galder Zamarre–o + * @since 4.2 + */ +@Test(groups = "functional", testName = "CacheRestartTest") +public class CacheRestartTest extends SingleCacheManagerTest { + + @Override + protected EmbeddedCacheManager createCacheManager() throws Exception { + return TestCacheManagerFactory.createLocalCacheManager(); + } + + public void testCacheStopFollowedByGetCache() { + Cache cache = cacheManager.getCache(); + cache.put("k", "v"); + cache.stop(); + Cache cache2 = cacheManager.getCache(); + cache2.put("k", "v2"); + assert "v2".equals(cache2.get("k")); + assert cache2.getStatus() == ComponentStatus.RUNNING : "Cache should be running, but it's not, it's status is " + cache2.getStatus(); + } + + public void testCacheStopFollowedByCacheOp() { + Cache cache = cacheManager.getCache(); + cache.put("k", "v"); + cache.stop(); + cache.put("k", "v2"); + assert "v2".equals(cache.get("k")); + assert cache.getStatus() == ComponentStatus.RUNNING : "Cache should be running, but it's not, it's status is " + cache.getStatus(); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCacheStopManagerStopFollowedByGetCache() { + EmbeddedCacheManager localCacheManager = TestCacheManagerFactory.createLocalCacheManager(); + try { + Cache cache = localCacheManager.getCache(); + cache.put("k", "v"); + cache.stop(); + localCacheManager.stop(); + localCacheManager.getCache(); + } finally { + TestingUtil.killCacheManagers(localCacheManager); + } + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCacheStopManagerStopFollowedByCacheOp() { + EmbeddedCacheManager localCacheManager = TestCacheManagerFactory.createLocalCacheManager(); + try { + Cache cache = localCacheManager.getCache(); + cache.put("k", "v"); + cache.stop(); + localCacheManager.stop(); + cache.put("k", "v2"); + } finally { + TestingUtil.killCacheManagers(localCacheManager); + } + } + +}