diff --git a/build.gradle b/build.gradle
index 57c343d04e..293206a5f1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -507,6 +507,7 @@ project("spring-context-support") {
optional(project(":spring-tx")) // for Quartz support
optional("javax.mail:mail:1.4.7")
optional("javax.cache:cache-api:1.0.0-PFD")
+ optional("com.google.guava:guava:14.0.1")
optional("net.sf.ehcache:ehcache-core:2.6.5")
optional("org.quartz-scheduler:quartz:1.8.6") {
exclude group: "org.slf4j", module: "slf4j-log4j12"
diff --git a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java
new file mode 100644
index 0000000000..edf76d2602
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCache.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2002-2013 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.cache.guava;
+
+import java.io.Serializable;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.SimpleValueWrapper;
+import org.springframework.util.Assert;
+
+/**
+ * Spring {@link Cache} adapter implementation on top of a
+ * Guava {@link com.google.common.cache.Cache} instance.
+ *
+ *
Requires Google Guava 12.0 or higher.
+ *
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+public class GuavaCache implements Cache {
+
+ private static final Object NULL_HOLDER = new NullHolder();
+
+ private final String name;
+
+ private final com.google.common.cache.Cache cache;
+
+ private final boolean allowNullValues;
+
+
+ /**
+ * Create a {@link GuavaCache} instance.
+ * @param name the name of the cache
+ * @param cache backing Guava Cache instance
+ */
+ public GuavaCache(String name, com.google.common.cache.Cache cache) {
+ this(name, cache, true);
+ }
+
+ /**
+ * Create a {@link GuavaCache} instance.
+ * @param name the name of the cache
+ * @param cache backing Guava Cache instance
+ * @param allowNullValues whether to accept and convert null values for this cache
+ */
+ public GuavaCache(String name, com.google.common.cache.Cache cache, boolean allowNullValues) {
+ Assert.notNull(name, "Name must not be null");
+ Assert.notNull(cache, "Cache must not be null");
+ this.name = name;
+ this.cache = cache;
+ this.allowNullValues = allowNullValues;
+ }
+
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public com.google.common.cache.Cache getNativeCache() {
+ return this.cache;
+ }
+
+ public boolean isAllowNullValues() {
+ return this.allowNullValues;
+ }
+
+ @Override
+ public ValueWrapper get(Object key) {
+ Object value = this.cache.getIfPresent(key);
+ return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T get(Object key, Class type) {
+ Object value = fromStoreValue(this.cache.getIfPresent(key));
+ if (type != null && !type.isInstance(value)) {
+ throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
+ }
+ return (T) value;
+ }
+
+ @Override
+ public void put(Object key, Object value) {
+ this.cache.put(key, toStoreValue(value));
+ }
+
+ @Override
+ public void evict(Object key) {
+ this.cache.invalidate(key);
+ }
+
+ @Override
+ public void clear() {
+ this.cache.invalidateAll();
+ }
+
+
+ /**
+ * Convert the given value from the internal store to a user value
+ * returned from the get method (adapting {@code null}).
+ * @param storeValue the store value
+ * @return the value to return to the user
+ */
+ protected Object fromStoreValue(Object storeValue) {
+ if (this.allowNullValues && storeValue == NULL_HOLDER) {
+ return null;
+ }
+ return storeValue;
+ }
+
+ /**
+ * Convert the given user value, as passed into the put method,
+ * to a value in the internal store (adapting {@code null}).
+ * @param userValue the given user value
+ * @return the value to store
+ */
+ protected Object toStoreValue(Object userValue) {
+ if (this.allowNullValues && userValue == null) {
+ return NULL_HOLDER;
+ }
+ return userValue;
+ }
+
+
+ @SuppressWarnings("serial")
+ private static class NullHolder implements Serializable {
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java
new file mode 100644
index 0000000000..cc08695006
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2002-2013 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.cache.guava;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheBuilderSpec;
+import com.google.common.cache.CacheLoader;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * {@link CacheManager} implementation that lazily builds {@link GuavaCache}
+ * instances for each {@link #getCache} request. Also supports a 'static' mode
+ * where the set of cache names is pre-defined through {@link #setCacheNames},
+ * with no dynamic creation of further cache regions at runtime.
+ *
+ * The configuration of the underlying cache can be fine-tuned through a
+ * Guava {@link CacheBuilder} or {@link CacheBuilderSpec}, passed into this
+ * CacheManager through {@link #setCacheBuilder}/{@link #setCacheBuilderSpec}.
+ * A {@link CacheBuilderSpec}-compliant expression value can also be applied
+ * via the {@link #setCacheSpecification "cacheSpecification"} bean property.
+ *
+ *
Requires Google Guava 12.0 or higher.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see GuavaCache
+ */
+public class GuavaCacheManager implements CacheManager, DisposableBean {
+
+ private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);
+
+ private boolean dynamic = true;
+
+ private CacheBuilder cacheBuilder = CacheBuilder.newBuilder();
+
+ private CacheLoader cacheLoader;
+
+
+ /**
+ * Construct a dynamic GuavaCacheManager,
+ * lazily creating cache instances as they are being requested.
+ */
+ public GuavaCacheManager() {
+ }
+
+ /**
+ * Construct a static GuavaCacheManager,
+ * managing caches for the specified cache names only.
+ */
+ public GuavaCacheManager(String... cacheNames) {
+ setCacheNames(Arrays.asList(cacheNames));
+ }
+
+
+ /**
+ * Specify the set of cache names for this CacheManager's 'static' mode.
+ * The number of caches and their names will be fixed after a call to this method,
+ * with no creation of further cache regions at runtime.
+ */
+ public void setCacheNames(Collection cacheNames) {
+ if (cacheNames != null) {
+ for (String name : cacheNames) {
+ this.cacheMap.put(name, createGuavaCache(name));
+ }
+ this.dynamic = false;
+ }
+ }
+
+ /**
+ * Set the Guava CacheBuilder to use for building each individual
+ * {@link GuavaCache} instance.
+ * @see #createNativeGuavaCache
+ * @see com.google.common.cache.CacheBuilder#build()
+ */
+ public void setCacheBuilder(CacheBuilder cacheBuilder) {
+ Assert.notNull(cacheBuilder, "CacheBuilder must not be null");
+ this.cacheBuilder = cacheBuilder;
+ }
+
+ /**
+ * Set the Guava CacheBuilderSpec to use for building each individual
+ * {@link GuavaCache} instance.
+ * @see #createNativeGuavaCache
+ * @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec)
+ */
+ public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) {
+ this.cacheBuilder = CacheBuilder.from(cacheBuilderSpec);
+ }
+
+ /**
+ * Set the Guava cache specification String to use for building each
+ * individual {@link GuavaCache} instance. The given value needs to
+ * comply with Guava's {@link CacheBuilderSpec} (see its javadoc).
+ * @see #createNativeGuavaCache
+ * @see com.google.common.cache.CacheBuilder#from(String)
+ */
+ public void setCacheSpecification(String cacheSpecification) {
+ this.cacheBuilder = CacheBuilder.from(cacheSpecification);
+ }
+
+ /**
+ * Set the Guava CacheLoader to use for building each individual
+ * {@link GuavaCache} instance, turning it into a LoadingCache.
+ * @see #createNativeGuavaCache
+ * @see com.google.common.cache.CacheBuilder#build(CacheLoader)
+ * @see com.google.common.cache.LoadingCache
+ */
+ public void setCacheLoader(CacheLoader cacheLoader) {
+ this.cacheLoader = cacheLoader;
+ }
+
+
+ @Override
+ public Collection getCacheNames() {
+ return Collections.unmodifiableSet(this.cacheMap.keySet());
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ Cache cache = this.cacheMap.get(name);
+ if (cache == null && this.dynamic) {
+ synchronized (this.cacheMap) {
+ cache = this.cacheMap.get(name);
+ if (cache == null) {
+ cache = createGuavaCache(name);
+ this.cacheMap.put(name, cache);
+ }
+ }
+ }
+ return cache;
+ }
+
+ /**
+ * Create a new GuavaCache instance for the specified cache name.
+ * @param name the name of the cache
+ * @return the Spring GuavaCache adapter (or a decorator thereof)
+ */
+ protected Cache createGuavaCache(String name) {
+ return new GuavaCache(name, createNativeGuavaCache(name));
+ }
+
+ /**
+ * Create a native Guava Cache instance for the specified cache name.
+ * @param name the name of the cache
+ * @return the native Guava Cache instance
+ */
+ protected com.google.common.cache.Cache createNativeGuavaCache(String name) {
+ if (this.cacheLoader != null) {
+ return this.cacheBuilder.build(this.cacheLoader);
+ }
+ else {
+ return this.cacheBuilder.build();
+ }
+ }
+
+
+ @Override
+ public void destroy() {
+ for (Cache cache : this.cacheMap.values()) {
+
+ }
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/guava/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/guava/package-info.java
new file mode 100644
index 0000000000..6f892820a9
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/guava/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Support classes for the open source cache in Google's
+ * Guava library,
+ * allowing to set up Guava caches within Spring's cache abstraction.
+ */
+package org.springframework.cache.guava;
diff --git a/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java
new file mode 100644
index 0000000000..0820d12f95
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2013 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.cache.guava;
+
+import org.junit.Test;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ */
+public class GuavaCacheManagerTests {
+
+ @Test
+ public void testDynamicMode() {
+ CacheManager cm = new GuavaCacheManager();
+ Cache cache1 = cm.getCache("c1");
+ assertTrue(cache1 instanceof GuavaCache);
+ Cache cache1again = cm.getCache("c1");
+ assertSame(cache1again, cache1);
+ Cache cache2 = cm.getCache("c2");
+ assertTrue(cache2 instanceof GuavaCache);
+ Cache cache2again = cm.getCache("c2");
+ assertSame(cache2again, cache2);
+ Cache cache3 = cm.getCache("c3");
+ assertTrue(cache3 instanceof GuavaCache);
+ Cache cache3again = cm.getCache("c3");
+ assertSame(cache3again, cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
+ }
+
+ @Test
+ public void testStaticMode() {
+ CacheManager cm = new GuavaCacheManager("c1", "c2");
+ Cache cache1 = cm.getCache("c1");
+ assertTrue(cache1 instanceof GuavaCache);
+ Cache cache1again = cm.getCache("c1");
+ assertSame(cache1again, cache1);
+ Cache cache2 = cm.getCache("c2");
+ assertTrue(cache2 instanceof GuavaCache);
+ Cache cache2again = cm.getCache("c2");
+ assertSame(cache2again, cache2);
+ Cache cache3 = cm.getCache("c3");
+ assertNull(cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
index 4dc8af0d7a..7294b5bd63 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
+import org.springframework.util.Assert;
/**
* Simple {@link Cache} implementation based on the core JDK
@@ -77,6 +78,8 @@ public class ConcurrentMapCache implements Cache {
* (adapting them to an internal null holder value)
*/
public ConcurrentMapCache(String name, ConcurrentMap store, boolean allowNullValues) {
+ Assert.notNull(name, "Name must not be null");
+ Assert.notNull(store, "Store must not be null");
this.name = name;
this.store = store;
this.allowNullValues = allowNullValues;
diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
index 35371d92d4..6c87d7832b 100644
--- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -31,8 +31,15 @@ import org.springframework.cache.CacheManager;
* the set of cache names is pre-defined through {@link #setCacheNames}, with no
* dynamic creation of further cache regions at runtime.
*
+ * Note: This is by no means a sophisticated CacheManager; it comes with no
+ * cache configuration options. However, it may be useful for testing or simple
+ * caching scenarios. For advanced local caching needs, consider
+ * {@link org.springframework.cache.guava.GuavaCacheManager} or
+ * {@link org.springframework.cache.ehcache.EhCacheCacheManager}.
+ *
* @author Juergen Hoeller
* @since 3.1
+ * @see ConcurrentMapCache
*/
public class ConcurrentMapCacheManager implements CacheManager {
diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
index cc0c963070..661e4a3d35 100644
--- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -43,11 +43,20 @@ public class ConcurrentMapCacheManagerTests {
assertTrue(cache3 instanceof ConcurrentMapCache);
Cache cache3again = cm.getCache("c3");
assertSame(cache3again, cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
}
@Test
public void testStaticMode() {
- ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager("c1", "c2");
+ CacheManager cm = new ConcurrentMapCacheManager("c1", "c2");
Cache cache1 = cm.getCache("c1");
assertTrue(cache1 instanceof ConcurrentMapCache);
Cache cache1again = cm.getCache("c1");
@@ -58,6 +67,15 @@ public class ConcurrentMapCacheManagerTests {
assertSame(cache2again, cache2);
Cache cache3 = cm.getCache("c3");
assertNull(cache3);
+
+ cache1.put("key1", "value1");
+ assertEquals("value1", cache1.get("key1").get());
+ cache1.put("key2", 2);
+ assertEquals(2, cache1.get("key2").get());
+ cache1.put("key3", null);
+ assertNull(cache1.get("key3").get());
+ cache1.evict("key3");
+ assertNull(cache1.get("key3"));
}
}