diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/MapRetryContextCache.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/MapRetryContextCache.java index 404e4d2cd..7bae045f2 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/MapRetryContextCache.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/MapRetryContextCache.java @@ -15,7 +15,6 @@ */ package org.springframework.batch.retry.policy; -import java.lang.ref.SoftReference; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,8 +23,7 @@ import org.springframework.batch.retry.RetryContext; /** * Map-based implementation of {@link RetryContextCache}. The map backing the - * cache of contexts is synchronized and its entries are soft-referenced, so may - * be garbage collected under pressure. + * cache of contexts is synchronized. * * @author Dave Syer * @@ -39,8 +37,7 @@ public class MapRetryContextCache implements RetryContextCache { */ public static final int DEFAULT_CAPACITY = 4096; - private Map> map = Collections - .synchronizedMap(new HashMap>()); + private Map map = Collections.synchronizedMap(new HashMap()); private int capacity; @@ -73,18 +70,11 @@ public class MapRetryContextCache implements RetryContextCache { } public boolean containsKey(Object key) { - if (!map.containsKey(key)) { - return false; - } - if (map.get(key).get() == null) { - // our reference was garbage collected - map.remove(key); - } return map.containsKey(key); } public RetryContext get(Object key) { - return map.get(key).get(); + return map.get(key); } public void put(Object key, RetryContext context) { @@ -93,7 +83,7 @@ public class MapRetryContextCache implements RetryContextCache { + "Do you need to re-consider the implementation of the key generator, " + "or the equals and hashCode of the items that failed?"); } - map.put(key, new SoftReference(context)); + map.put(key, context); } public void remove(Object key) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCache.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCache.java new file mode 100644 index 000000000..3e52e64f4 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCache.java @@ -0,0 +1,104 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed 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.springframework.batch.retry.policy; + +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.retry.RetryContext; + +/** + * Map-based implementation of {@link RetryContextCache}. The map backing the + * cache of contexts is synchronized and its entries are soft-referenced, so may + * be garbage collected under pressure. + * + * @see MapRetryContextCache for non-soft referenced version + * + * @author Dave Syer + * + */ +public class SoftReferenceMapRetryContextCache implements RetryContextCache { + + /** + * Default value for maximum capacity of the cache. This is set to a + * reasonably low value (4096) to avoid users inadvertently filling the + * cache with item keys that are inconsistent. + */ + public static final int DEFAULT_CAPACITY = 4096; + + private Map> map = Collections + .synchronizedMap(new HashMap>()); + + private int capacity; + + /** + * Create a {@link SoftReferenceMapRetryContextCache} with default capacity. + */ + public SoftReferenceMapRetryContextCache() { + this(DEFAULT_CAPACITY); + } + + /** + * @param defaultCapacity + */ + public SoftReferenceMapRetryContextCache(int defaultCapacity) { + super(); + this.capacity = defaultCapacity; + } + + /** + * Public setter for the capacity. Prevents the cache from growing + * unboundedly if items that fail are misidentified and two references to an + * identical item actually do not have the same key. This can happen when + * users implement equals and hashCode based on mutable fields, for + * instance. + * + * @param capacity the capacity to set + */ + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + public boolean containsKey(Object key) { + if (!map.containsKey(key)) { + return false; + } + if (map.get(key).get() == null) { + // our reference was garbage collected + map.remove(key); + } + return map.containsKey(key); + } + + public RetryContext get(Object key) { + return map.get(key).get(); + } + + public void put(Object key, RetryContext context) { + if (map.size() >= capacity) { + throw new RetryCacheCapacityExceededException("Retry cache capacity limit breached. " + + "Do you need to re-consider the implementation of the key generator, " + + "or the equals and hashCode of the items that failed?"); + } + map.put(key, new SoftReference(context)); + } + + public void remove(Object key) { + map.remove(key); + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCacheTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCacheTests.java new file mode 100644 index 000000000..25dbb0983 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/SoftReferenceMapRetryContextCacheTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed 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.springframework.batch.retry.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.batch.retry.context.RetryContextSupport; + +public class SoftReferenceMapRetryContextCacheTests { + + SoftReferenceMapRetryContextCache cache = new SoftReferenceMapRetryContextCache(); + + @Test + public void testPut() { + RetryContextSupport context = new RetryContextSupport(null); + cache.put("foo", context); + assertEquals(context, cache.get("foo")); + } + + @Test(expected=RetryCacheCapacityExceededException.class) + public void testPutOverLimit() { + RetryContextSupport context = new RetryContextSupport(null); + cache.setCapacity(1); + cache.put("foo", context); + cache.put("foo", context); + } + + @Test + public void testRemove() { + assertFalse(cache.containsKey("foo")); + RetryContextSupport context = new RetryContextSupport(null); + cache.put("foo", context); + assertTrue(cache.containsKey("foo")); + cache.remove("foo"); + assertFalse(cache.containsKey("foo")); + } + +}