diff --git a/build.gradle b/build.gradle
index 7002f17e8eab..c515f12abdc7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -93,6 +93,7 @@ configure(allprojects) { project ->
"http://aopalliance.sourceforge.net/doc/",
"http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
"http://ehcache.org/apidocs/",
+ "http://docs.jboss.org/infinispan/5.2/apidocs/",
"http://quartz-scheduler.org/api/2.1.7/",
"http://jackson.codehaus.org/1.9.12/javadoc/",
"http://fasterxml.github.com/jackson-core/javadoc/2.2.0/",
@@ -395,6 +396,10 @@ project("spring-jdbc") {
project("spring-context-support") {
description = "Spring Context Support"
+ repositories {
+ maven { url "https://repository.jboss.org/nexus/content/repositories/releases/" } // infinispan
+ }
+
dependencies {
compile(project(":spring-core"))
compile(project(":spring-beans"))
@@ -404,6 +409,7 @@ project("spring-context-support") {
optional("javax.mail:mail:1.4.7")
optional("javax.cache:cache-api:0.6")
optional("net.sf.ehcache:ehcache-core:2.6.5")
+ optional("org.infinispan:infinispan-core:5.2.6.Final")
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/infinispan/InfinispanCache.java b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCache.java
new file mode 100644
index 000000000000..cf38841b85e3
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCache.java
@@ -0,0 +1,129 @@
+/*
+ * 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.infinispan;
+
+import org.infinispan.lifecycle.ComponentStatus;
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.SimpleValueWrapper;
+import org.springframework.util.Assert;
+
+/**
+ * {@link org.springframework.cache.Cache} implementation on top of an
+ * {@link org.infinispan.Cache} instance.
+ *
+ * @author Philippe Marschall
+ * @since 4.0
+ */
+public class InfinispanCache implements Cache {
+
+ private static final Object NULL_HOLDER = NullHolder.INSTANE;
+
+ @SuppressWarnings("rawtypes")
+ private final org.infinispan.Cache cache;
+
+ private final boolean allowNullValues;
+
+
+ /**
+ * Create an {@link org.springframework.cache.infinispan.InfinispanCache} instance.
+ * @param infinispanCache backing Infinispan Cache instance
+ */
+ public InfinispanCache(org.infinispan.Cache infinispanCache) {
+ this(infinispanCache, true);
+ }
+
+ /**
+ * Create an {@link org.springframework.cache.infinispan.InfinispanCache} instance.
+ * @param infinispanCache backing Infinispan Cache instance
+ * @param allowNullValues whether to accept and convert null values for this cache
+ */
+ public InfinispanCache(org.infinispan.Cache infinispanCache, boolean allowNullValues) {
+ Assert.notNull(infinispanCache, "Cache must not be null");
+ ComponentStatus status = infinispanCache.getStatus();
+ Assert.isTrue(status == ComponentStatus.RUNNING,
+ "A 'running' cache is required - current cache is " + status);
+ this.cache = infinispanCache;
+ this.allowNullValues = allowNullValues;
+ }
+
+ @Override
+ public String getName() {
+ return this.cache.getName();
+ }
+
+ @Override
+ public org.infinispan.Cache getNativeCache() {
+ return this.cache;
+ }
+
+ @Override
+ public ValueWrapper get(Object key) {
+ Object value = this.cache.get(key);
+ return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void put(Object key, Object value) {
+ this.cache.put(key, toStoreValue(value));
+ }
+
+ @Override
+ public void evict(Object key) {
+ this.cache.remove(key);
+ }
+
+ @Override
+ public void clear() {
+ this.cache.clear();
+ }
+
+
+
+ /**
+ * 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")
+ static enum NullHolder {
+ INSTANE;
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManager.java
new file mode 100644
index 000000000000..2931b3fa7311
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManager.java
@@ -0,0 +1,123 @@
+/*
+ * 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.infinispan;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.infinispan.lifecycle.ComponentStatus;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.springframework.cache.Cache;
+import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * {@link org.springframework.cache.CacheManager} implementation
+ * backed by a Infinispan {@link EmbeddedCacheManager}.
+ *
+ * @author Philippe Marschall
+ * @since 4.0
+ */
+public class InfinispanCacheManager extends AbstractTransactionSupportingCacheManager {
+
+ private EmbeddedCacheManager cacheManager;
+
+ private boolean allowNullValues = true;
+
+
+ /**
+ * Create a new InfinispanCacheManager, setting the target Infinispan CacheManager
+ * through the {@link #setCacheManager} bean property.
+ */
+ public InfinispanCacheManager() {
+ }
+
+ /**
+ * Create a new InfinispanCacheManager for the given backing Infinispan.
+ * @param cacheManager the backing Infinispan {@link EmbeddedCacheManager}
+ */
+ public InfinispanCacheManager(EmbeddedCacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+
+ /**
+ * Set the backing Infinispan {@link EmbeddedCacheManager}.
+ */
+ public void setCacheManager(EmbeddedCacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ /**
+ * Return the backing Infinispan {@link EmbeddedCacheManager}.
+ */
+ public EmbeddedCacheManager getCacheManager() {
+ return this.cacheManager;
+ }
+
+ /**
+ * Specify whether to accept and convert null values for all caches
+ * in this cache manager.
+ *
Default is "true", despite Infinispan itself not supporting null values.
+ * An internal holder object will be used to store user-level null values.
+ */
+ public void setAllowNullValues(boolean allowNullValues) {
+ this.allowNullValues = allowNullValues;
+ }
+
+ /**
+ * Return whether this cache manager accepts and converts null values
+ * for all of its caches.
+ */
+ public boolean isAllowNullValues() {
+ return this.allowNullValues;
+ }
+
+
+ @Override
+ protected Collection loadCaches() {
+ Assert.notNull(this.cacheManager, "A backing CacheManager is required");
+ ComponentStatus status = this.cacheManager.getStatus();
+ Assert.isTrue(ComponentStatus.RUNNING == status,
+ "A 'running' Infinispan CacheManager is required - current cache is " + status);
+
+ Set cacheNames = this.cacheManager.getCacheNames();
+ Collection caches = new LinkedHashSet(cacheNames.size());
+
+ for (String cacheName : cacheNames) {
+ org.infinispan.Cache infinispanCache = this.cacheManager.getCache(cacheName, true);
+ caches.add(new InfinispanCache(infinispanCache, this.allowNullValues));
+ }
+ return caches;
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ Cache cache = super.getCache(name);
+ if (cache == null) {
+ // check the Infinispan cache again
+ // (in case the cache was added at runtime)
+ org.infinispan.Cache infinispanCache = this.cacheManager.getCache(name, false);
+ if (infinispanCache != null) {
+ cache = new InfinispanCache(infinispanCache, this.allowNullValues);
+ addCache(cache);
+ }
+ }
+ return cache;
+ }
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManagerFactoryBean.java
new file mode 100644
index 000000000000..e4561745cc42
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/infinispan/InfinispanCacheManagerFactoryBean.java
@@ -0,0 +1,93 @@
+/*
+ * 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.infinispan;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+
+/**
+ * {@link FactoryBean} for a Infinispan {@link EmbeddedCacheManager}.
+ *
+ * This class is intended to be used in conjunction with Spring XML configuration and
+ * Infinispan XML configuration. While it does work with Spring Java configuration in
+ * such cases you're likely better off instantiating the cache manager yourself and using
+ *
+ * Infinispan fluent configuration.
+ *
+ * @author Philippe Marschall
+ * @since 4.0
+ */
+public class InfinispanCacheManagerFactoryBean
+ implements FactoryBean, InitializingBean, DisposableBean {
+
+ private Resource configLocation;
+
+ private EmbeddedCacheManager cacheManager;
+
+
+ /**
+ * Set the location of the Infinispan config file. A typical value is "/WEB-INF/infinispan.xml".
+ * @see org.infinispan.manager.DefaultCacheManager#DefaultCacheManager(java.io.InputStream)
+ */
+ public void setConfigLocation(Resource configLocation) {
+ this.configLocation = configLocation;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws IOException {
+ if (this.configLocation != null) {
+ InputStream configurationStream = this.configLocation.getInputStream();
+ try {
+ this.cacheManager = new DefaultCacheManager(configurationStream, true);
+ } finally {
+ configurationStream.close();
+ }
+ } else {
+ this.cacheManager = new DefaultCacheManager(true);
+ }
+ }
+
+
+ @Override
+ public EmbeddedCacheManager getObject() {
+ return this.cacheManager;
+ }
+
+ @Override
+ public Class extends EmbeddedCacheManager> getObjectType() {
+ return this.cacheManager != null ? this.cacheManager.getClass() : EmbeddedCacheManager.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ @Override
+ public void destroy() {
+ this.cacheManager.stop();
+ }
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/infinispan/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/infinispan/package-info.java
new file mode 100644
index 000000000000..7ec2f08ae48f
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/infinispan/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Support classes for the open source cache
+ * Infinispan,
+ * allowing to set up an Infinispan CacheManager and Caches
+ * as beans in a Spring context.
+ */
+package org.springframework.cache.infinispan;
diff --git a/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanCacheTests.java
new file mode 100644
index 000000000000..ac6fd1be4ddc
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanCacheTests.java
@@ -0,0 +1,114 @@
+/*
+ * 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.infinispan;
+
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.cache.Cache;
+import org.springframework.tests.Assume;
+import org.springframework.tests.TestGroup;
+
+import static java.util.concurrent.TimeUnit.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Philippe Marschall
+ */
+public class InfinispanCacheTests {
+
+ protected final static String CACHE_NAME = "testCache";
+
+ protected org.infinispan.Cache nativeCache;
+
+ protected EmbeddedCacheManager cacheManager;
+
+ protected Cache cache;
+
+
+ @Before
+ public void setUp() throws Exception {
+ cacheManager = new DefaultCacheManager("org/springframework/cache/infinispan/testInfinispan.xml", true);
+ nativeCache = cacheManager.getCache(CACHE_NAME, true);
+ cache = new InfinispanCache(nativeCache);
+ cache.clear();
+ }
+
+ @After
+ public void tearDown() {
+ nativeCache.stop();
+ cacheManager.stop();
+ }
+
+
+ @Test
+ public void testCacheName() {
+ assertEquals(CACHE_NAME, cache.getName());
+ }
+
+ @Test
+ public void testNativeCache() {
+ assertSame(nativeCache, cache.getNativeCache());
+ }
+
+ @Test
+ public void testCachePut() {
+ Object key = "enescu";
+ Object value = "george";
+
+ assertNull(cache.get(key));
+ cache.put(key, value);
+ assertEquals(value, cache.get(key).get());
+ }
+
+ @Test
+ public void testCacheRemove() {
+ Object key = "enescu";
+ Object value = "george";
+
+ assertNull(cache.get(key));
+ cache.put(key, value);
+ }
+
+ @Test
+ public void testCacheClear() {
+ assertNull(cache.get("enescu"));
+ cache.put("enescu", "george");
+ assertNull(cache.get("vlaicu"));
+ cache.put("vlaicu", "aurel");
+ cache.clear();
+ assertNull(cache.get("vlaicu"));
+ assertNull(cache.get("enescu"));
+ }
+
+ @Test
+ public void testExpiredElements() throws Exception {
+ Assume.group(TestGroup.LONG_RUNNING);
+ String key = "brancusi";
+ String value = "constantin";
+ // ttl = 10s
+ nativeCache.put(key, value, 3, SECONDS);
+
+ assertEquals(value, cache.get(key).get());
+ // wait for the entry to expire
+ Thread.sleep(5 * 1000);
+ assertNull(cache.get(key));
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanSupportTests.java b/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanSupportTests.java
new file mode 100644
index 000000000000..ced6f7351170
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/infinispan/InfinispanSupportTests.java
@@ -0,0 +1,67 @@
+/*
+ * 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.infinispan;
+
+import org.infinispan.eviction.EvictionStrategy;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Philippe Marschall
+ */
+public class InfinispanSupportTests {
+
+ @Test
+ public void testLoadingBlankCacheManager() throws Exception {
+ InfinispanCacheManagerFactoryBean cacheManagerFb = new InfinispanCacheManagerFactoryBean();
+ assertEquals(EmbeddedCacheManager.class, cacheManagerFb.getObjectType());
+ assertTrue("Singleton property", cacheManagerFb.isSingleton());
+ cacheManagerFb.afterPropertiesSet();
+ try {
+ EmbeddedCacheManager cm = cacheManagerFb.getObject();
+ assertThat("Loaded CacheManager with no caches", cm.getCacheNames(), empty());
+ org.infinispan.Cache myCache1 = cm.getCache("myCache1", false);
+ assertNull("No myCache1 defined", myCache1);
+ }
+ finally {
+ cacheManagerFb.destroy();
+ }
+ }
+
+ @Test
+ public void testLoadingCacheManagerFromConfigFile() throws Exception {
+ InfinispanCacheManagerFactoryBean cacheManagerFb = new InfinispanCacheManagerFactoryBean();
+ cacheManagerFb.setConfigLocation(new ClassPathResource("testInfinispan.xml", getClass()));
+ cacheManagerFb.afterPropertiesSet();
+ try {
+ EmbeddedCacheManager cm = cacheManagerFb.getObject();
+ assertThat("Correct number of caches loaded", cm.getCacheNames(), hasSize(1));
+ org.infinispan.Cache myCache1 = cm.getCache("myCache1");
+ assertNotNull("No myCache1 defined", myCache1);
+ assertEquals("myCache1 is not LIRS", myCache1.getCacheConfiguration().eviction().strategy(), EvictionStrategy.LIRS);
+ assertTrue("myCache1.maxElements == 300", myCache1.getCacheConfiguration().eviction().maxEntries() == 300);
+ }
+ finally {
+ cacheManagerFb.destroy();
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/resources/org/springframework/cache/infinispan/testInfinispan.xml b/spring-context-support/src/test/resources/org/springframework/cache/infinispan/testInfinispan.xml
new file mode 100644
index 000000000000..efca56afd9f3
--- /dev/null
+++ b/spring-context-support/src/test/resources/org/springframework/cache/infinispan/testInfinispan.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file