IGNITE-13809 Add thin client support to ignite-spring-cache - Fixes #46.
Signed-off-by: Aleksey Plekhanov <plehanov.alex@gmail.com>
diff --git a/modules/spring-cache-ext/pom.xml b/modules/spring-cache-ext/pom.xml
index 0b5a190..05e0204 100644
--- a/modules/spring-cache-ext/pom.xml
+++ b/modules/spring-cache-ext/pom.xml
@@ -50,6 +50,12 @@
</dependency>
<dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-spring-data-commons</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>${xstream.version}</version>
diff --git a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/AbstractCacheManager.java b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/AbstractCacheManager.java
new file mode 100644
index 0000000..5974806
--- /dev/null
+++ b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/AbstractCacheManager.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.cache.spring;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+/**
+ * Represents abstract {@link CacheManager} implementation that hand over responsibility to create new cache instances
+ * and synchronization objects for cache value computations to its inheritors.
+ */
+public abstract class AbstractCacheManager implements CacheManager {
+ /** Caches mapped to their names. */
+ private final Map<String, SpringCache> caches = new ConcurrentHashMap<>();
+
+ /** {@inheritDoc} */
+ @Override public Cache getCache(String name) {
+ return caches.computeIfAbsent(name, k -> createCache(name));
+ }
+
+ /** {@inheritDoc} */
+ @Override public Collection<String> getCacheNames() {
+ return Collections.unmodifiableSet(caches.keySet());
+ }
+
+ /** Creates {@link SpringCache} instance with specified name. */
+ protected abstract SpringCache createCache(String name);
+
+ /** Gets {@link Lock} to synchronize value calculation for specified cache and key. */
+ protected abstract Lock getSyncLock(String cache, Object key);
+}
diff --git a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManager.java b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManager.java
new file mode 100644
index 0000000..49ed32b
--- /dev/null
+++ b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManager.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.cache.spring;
+
+import java.util.concurrent.locks.Lock;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.ClientCacheConfiguration;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.springdata.proxy.IgniteCacheClientProxy;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * Represents implementation of {@link AbstractCacheManager} that uses thin client to connect to an Ignite cluster
+ * and obtain an Ignite cache instance. It requires {@link IgniteClient} instance or {@link ClientConfiguration} to be
+ * set before manager use (see {@link #setClientInstance(IgniteClient),
+ * {@link #setClientConfiguration(ClientConfiguration)}}).
+ *
+ *
+ * Note that Spring Cache synchronous mode ({@link Cacheable#sync}) is not supported by the current manager
+ * implementation. Instead, use an {@link SpringCacheManager} that uses an Ignite thick client to connect to Ignite cluster.
+ *
+ * You can provide Ignite client instance to a Spring configuration XML file, like below:
+ *
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:cache="http://www.springframework.org/schema/cache"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
+ * <!--
+ * Note that org.apache.ignite.IgniteClientSpringBean is available since Ignite 2.11.0 version.
+ * For Ignite versions earlier than 2.11.0 org.apache.ignite.client.IgniteClient bean should be created
+ * manually with concern of its connection to the Ignite cluster.
+ * -->
+ * <bean id="igniteClient" class="org.apache.ignite.IgniteClientSpringBean">
+ * <property name="clientConfiguration">
+ * <bean class="org.apache.ignite.configuration.ClientConfiguration">
+ * <property name="addresses">
+ * <list>
+ * <value>127.0.0.1:10800</value>
+ * </list>
+ * </property>
+ * </bean>
+ * </property>
+ * </bean>
+ *
+ * <!-- Provide Ignite client instance. -->
+ * <bean id="cacheManager" class="org.apache.ignite.cache.spring.IgniteClientSpringCacheManager">
+ * <property name="clientInstance" ref="igniteClient"/>
+ * </bean>
+ *
+ * <!-- Use annotation-driven cache configuration. -->
+ * <cache:annotation-driven/>
+ * </beans>
+ * </pre>
+ *
+ * Or you can provide Ignite client configuration, like below:
+ *
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:cache="http://www.springframework.org/schema/cache"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
+ * <!-- Provide Ignite client instance. -->
+ * <bean id="cacheManager" class="org.apache.ignite.cache.spring.IgniteClientSpringCacheManager">
+ * <property name="clientConfiguration">
+ * <bean class="org.apache.ignite.configuration.ClientConfiguration">
+ * <property name="addresses">
+ * <list>
+ * <value>127.0.0.1:10800</value>
+ * </list>
+ * </property>
+ * </bean>
+ * </property>
+ * </bean>
+ *
+ * <!-- Use annotation-driven cache configuration. -->
+ * <cache:annotation-driven/>
+ * </beans>
+ * </pre>
+ */
+public class IgniteClientSpringCacheManager extends AbstractCacheManager implements DisposableBean,
+ ApplicationListener<ContextRefreshedEvent>
+{
+ /** Ignite client instance. */
+ private IgniteClient cli;
+
+ /** Ignite client configuration. */
+ private ClientConfiguration cliCfg;
+
+ /** Dynamic Ignite cache configuration template. */
+ private ClientCacheConfiguration dynamicCacheCfg;
+
+ /** Flag that indicates whether Ignite client instance was set explicitly. */
+ private boolean externalCliInstance;
+
+ /** Gets Ignite client instance. */
+ public IgniteClient getClientInstance() {
+ return cli;
+ }
+
+ /** Sets Ignite client instance. */
+ public IgniteClientSpringCacheManager setClientInstance(IgniteClient cli) {
+ A.notNull(cli, "cli");
+
+ this.cli = cli;
+
+ externalCliInstance = true;
+
+ return this;
+ }
+
+ /** Gets Ignite client configuration. */
+ public ClientConfiguration getClientConfiguration() {
+ return cliCfg;
+ }
+
+ /** Sets Ignite client configuration that will be used to start the client instance by the manager. */
+ public IgniteClientSpringCacheManager setClientConfiguration(ClientConfiguration cliCfg) {
+ this.cliCfg = cliCfg;
+
+ return this;
+ }
+
+ /** Gets dynamic Ignite cache configuration template. */
+ public ClientCacheConfiguration getDynamicCacheConfiguration() {
+ // To avoid copying the dynamic cache configuration each time as we only change its name.
+ return dynamicCacheCfg == null ? null : dynamicCacheCfg.setName(null);
+ }
+
+ /**
+ * Sets the Ignite cache configurations template that will be used to start the Ignite cache with the name
+ * requested by the Spring Cache Framework if one does not exist. Note that provided cache name will be erased and
+ * replaced with requested one.
+ */
+ public IgniteClientSpringCacheManager setDynamicCacheConfiguration(ClientCacheConfiguration dynamicCacheCfg) {
+ this.dynamicCacheCfg = dynamicCacheCfg;
+
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected synchronized SpringCache createCache(String name) {
+ ClientCacheConfiguration ccfg = dynamicCacheCfg == null ? new ClientCacheConfiguration() : dynamicCacheCfg;
+
+ ClientCache<Object, Object> cache = cli.getOrCreateCache(ccfg.setName(name));
+
+ return new SpringCache(new IgniteCacheClientProxy<>(cache), this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void destroy() throws Exception {
+ if (!externalCliInstance)
+ cli.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected Lock getSyncLock(String cache, Object key) {
+ throw new UnsupportedOperationException("Synchronous mode is not supported for the Ignite Spring Cache" +
+ " implementation that uses a thin client to connecting to an Ignite cluster.");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onApplicationEvent(ContextRefreshedEvent evt) {
+ if (cli != null)
+ return;
+
+ if (cliCfg == null) {
+ throw new IllegalArgumentException("Neither client instance nor client configuration is specified." +
+ " Set the 'clientInstance' property if you already have an Ignite client instance running," +
+ " or set the 'clientConfiguration' property if an Ignite client instance need to be started" +
+ " implicitly by the manager.");
+ }
+
+ cli = Ignition.startClient(cliCfg);
+ }
+}
diff --git a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCache.java b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCache.java
index 9a8f2a8..0052b9e 100644
--- a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCache.java
+++ b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCache.java
@@ -19,8 +19,8 @@
import java.io.Serializable;
import java.util.concurrent.Callable;
-import org.apache.ignite.IgniteCache;
-import org.apache.ignite.IgniteLock;
+import java.util.concurrent.locks.Lock;
+import org.apache.ignite.springdata.proxy.IgniteCacheProxy;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
@@ -32,16 +32,16 @@
private static final Object NULL = new NullValue();
/** */
- private final IgniteCache<Object, Object> cache;
+ private final IgniteCacheProxy<Object, Object> cache;
/** */
- private final SpringCacheManager mgr;
+ private final AbstractCacheManager mgr;
/**
* @param cache Cache.
* @param mgr Manager
*/
- SpringCache(IgniteCache<Object, Object> cache, SpringCacheManager mgr) {
+ SpringCache(IgniteCacheProxy<Object, Object> cache, AbstractCacheManager mgr) {
assert cache != null;
this.cache = cache;
@@ -55,7 +55,7 @@
/** {@inheritDoc} */
@Override public Object getNativeCache() {
- return cache;
+ return cache.delegate();
}
/** {@inheritDoc} */
@@ -66,7 +66,6 @@
}
/** {@inheritDoc} */
- @SuppressWarnings("unchecked")
@Override public <T> T get(Object key, Class<T> type) {
Object val = cache.get(key);
@@ -81,12 +80,11 @@
}
/** {@inheritDoc} */
- @SuppressWarnings("unchecked")
@Override public <T> T get(final Object key, final Callable<T> valLdr) {
Object val = cache.get(key);
if (val == null) {
- IgniteLock lock = mgr.getSyncLock(cache.getName(), key);
+ Lock lock = mgr.getSyncLock(cache.getName(), key);
lock.lock();
@@ -154,11 +152,13 @@
return new SimpleValueWrapper(unwrapNull(val));
}
+ /** */
private static Object unwrapNull(Object val) {
return NULL.equals(val) ? null : val;
}
- private <T> Object wrapNull(T val) {
+ /** */
+ private static <T> Object wrapNull(T val) {
return val == null ? NULL : val;
}
diff --git a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCacheManager.java b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCacheManager.java
index 80f62d7..62622f6 100644
--- a/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCacheManager.java
+++ b/modules/spring-cache-ext/src/main/java/org/apache/ignite/cache/spring/SpringCacheManager.java
@@ -17,12 +17,10 @@
package org.apache.ignite.cache.spring;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLock;
import org.apache.ignite.IgniteSpring;
@@ -31,8 +29,8 @@
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.NearCacheConfiguration;
import org.apache.ignite.internal.util.typedef.internal.U;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
+import org.apache.ignite.springdata.proxy.IgniteCacheProxyImpl;
+import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
@@ -142,16 +140,15 @@
* Ignite distribution, and all these nodes will participate
* in caching the data.
*/
-public class SpringCacheManager implements CacheManager, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
+public class SpringCacheManager extends AbstractCacheManager implements ApplicationListener<ContextRefreshedEvent>,
+ ApplicationContextAware, DisposableBean
+{
/** Default locks count. */
private static final int DEFAULT_LOCKS_COUNT = 512;
/** IgniteLock name prefix. */
private static final String SPRING_LOCK_NAME_PREFIX = "springSync";
- /** Caches map. */
- private final ConcurrentMap<String, SpringCache> caches = new ConcurrentHashMap<>();
-
/** Grid configuration file path. */
private String cfgPath;
@@ -179,6 +176,9 @@
/** Locks for value loading to support sync option. */
private ConcurrentHashMap<Integer, IgniteLock> locks = new ConcurrentHashMap<>();
+ /** Flag that indicates whether external Ignite instance is configured. */
+ private boolean externalIgniteInstance;
+
/** {@inheritDoc} */
@Override public void setApplicationContext(ApplicationContext ctx) {
this.springCtx = ctx;
@@ -329,8 +329,11 @@
}
else if (cfg != null)
ignite = IgniteSpring.start(cfg, springCtx);
- else
+ else {
ignite = Ignition.ignite(igniteInstanceName);
+
+ externalIgniteInstance = true;
+ }
}
catch (IgniteCheckedException e) {
throw U.convertException(e);
@@ -339,51 +342,34 @@
}
/** {@inheritDoc} */
- @Override public Cache getCache(String name) {
- assert ignite != null;
+ @Override protected SpringCache createCache(String name) {
+ CacheConfiguration<Object, Object> cacheCfg = dynamicCacheCfg != null ?
+ new CacheConfiguration<>(dynamicCacheCfg) : new CacheConfiguration<>();
- SpringCache cache = caches.get(name);
+ NearCacheConfiguration<Object, Object> nearCacheCfg = dynamicNearCacheCfg != null ?
+ new NearCacheConfiguration<>(dynamicNearCacheCfg) : null;
- if (cache == null) {
- CacheConfiguration<Object, Object> cacheCfg = dynamicCacheCfg != null ?
- new CacheConfiguration<>(dynamicCacheCfg) : new CacheConfiguration<>();
+ cacheCfg.setName(name);
- NearCacheConfiguration<Object, Object> nearCacheCfg = dynamicNearCacheCfg != null ?
- new NearCacheConfiguration<>(dynamicNearCacheCfg) : null;
+ IgniteCache<Object, Object> cache = nearCacheCfg != null
+ ? ignite.getOrCreateCache(cacheCfg, nearCacheCfg)
+ : ignite.getOrCreateCache(cacheCfg);
- cacheCfg.setName(name);
-
- cache = new SpringCache(nearCacheCfg != null ? ignite.getOrCreateCache(cacheCfg, nearCacheCfg) :
- ignite.getOrCreateCache(cacheCfg), this);
-
- SpringCache old = caches.putIfAbsent(name, cache);
-
- if (old != null)
- cache = old;
- }
-
- return cache;
+ return new SpringCache(new IgniteCacheProxyImpl<>(cache), this);
}
/** {@inheritDoc} */
- @Override public Collection<String> getCacheNames() {
- assert ignite != null;
-
- return new ArrayList<>(caches.keySet());
- }
-
- /**
- * Provides {@link org.apache.ignite.IgniteLock} for specified cache name and key.
- *
- * @param name cache name
- * @param key key
- * @return {@link org.apache.ignite.IgniteLock}
- */
- IgniteLock getSyncLock(String name, Object key) {
+ @Override protected IgniteLock getSyncLock(String name, Object key) {
int hash = Objects.hash(name, key);
final int idx = hash % getLocksCount();
return locks.computeIfAbsent(idx, i -> ignite.reentrantLock(SPRING_LOCK_NAME_PREFIX + idx, true, false, true));
}
+
+ /** {@inheritDoc} */
+ @Override public void destroy() {
+ if (!externalIgniteInstance)
+ ignite.close();
+ }
}
diff --git a/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManagerTest.java b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManagerTest.java
new file mode 100644
index 0000000..8f84e55
--- /dev/null
+++ b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/IgniteClientSpringCacheManagerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.cache.spring;
+
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientCacheConfiguration;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.BinaryConfiguration;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.apache.ignite.configuration.ClientConnectorConfiguration.DFLT_PORT;
+
+/** Tests Spring Cache manager implementation that uses thin client to connect to the Ignite cluster. */
+public class IgniteClientSpringCacheManagerTest extends GridSpringCacheManagerAbstractTest {
+ /** */
+ private AbstractApplicationContext ctx;
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ cfg.setCacheConfiguration(new CacheConfiguration<>(CACHE_NAME));
+ cfg.setBinaryConfiguration(new BinaryConfiguration().setCompactFooter(true));
+
+ return cfg;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ startGrid();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+
+ ctx = new AnnotationConfigApplicationContext(ClientInstanceApplicationContext.class);
+
+ svc = ctx.getBean(GridSpringCacheTestService.class);
+ dynamicSvc = ctx.getBean(GridSpringDynamicCacheTestService.class);
+
+ svc.reset();
+ dynamicSvc.reset();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() {
+ grid().cache(CACHE_NAME).removeAll();
+
+ grid().destroyCache(DYNAMIC_CACHE_NAME);
+
+ ctx.stop();
+ }
+
+ /**
+ * Tests that {@link IgniteClientSpringCacheManager} successfully creates {@link IgniteClient} instance with
+ * provided {@link ClientConfiguration}.
+ */
+ @Test
+ public void testClientConfiguration() {
+ try (
+ AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(
+ ClientConfigurationApplicationContext.class)
+ ) {
+ IgniteClient cli = ctx.getBean(IgniteClientSpringCacheManager.class).getClientInstance();
+
+ assertNotNull(cli);
+ assertEquals(1, cli.cluster().nodes().size());
+ }
+ }
+
+ /** Tests {@link IgniteClientSpringCacheManager} behaviour in case no connection configuration is specified. */
+ @Test
+ @SuppressWarnings("EmptyTryBlock")
+ public void testOmittedConnectionConfiguration() {
+ GridTestUtils.assertThrowsAnyCause(
+ log,
+ () -> {
+ try (
+ AbstractApplicationContext ignored = new AnnotationConfigApplicationContext(
+ InvalidConnectionConfigurationApplicationContext.class)
+ ) {
+ // No-op.
+ }
+
+ return null;
+ },
+ IllegalArgumentException.class,
+ "Neither client instance nor client configuration is specified.");
+ }
+
+ /** Tests {@link IgniteClientSpringCacheManager} configuration approach through XML file. */
+ @Test
+ public void testCacheManagerXmlConfiguration() {
+ try (
+ AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
+ "org/apache/ignite/cache/spring/ignite-client-spring-caching.xml")
+ ) {
+ IgniteClientSpringCacheManager mgr = ctx.getBean(IgniteClientSpringCacheManager.class);
+
+ assertNotNull(mgr.getClientConfiguration());
+
+ IgniteClient cli = mgr.getClientInstance();
+
+ assertNotNull(cli);
+ assertEquals(1, cli.cluster().nodes().size());
+
+ ClientCacheConfiguration cfg = mgr.getDynamicCacheConfiguration();
+
+ assertEquals(2, cfg.getBackups());
+ }
+ }
+
+ /** Test {@link Cacheable} annotation with {@code sync} mode enabled. */
+ @Test
+ public void testSyncMode() {
+ GridTestUtils.assertThrowsAnyCause(
+ log,
+ () -> {
+ dynamicSvc.cacheableSync(0);
+
+ return null;
+ },
+ UnsupportedOperationException.class,
+ "Synchronous mode is not supported for the Ignite Spring Cache implementation that uses a thin client" +
+ " to connecting to an Ignite cluster."
+ );
+ }
+
+ /** */
+ @Configuration
+ @EnableCaching
+ public static class ClientInstanceApplicationContext extends CachingConfigurerSupport {
+ /** */
+ @Bean
+ public GridSpringCacheTestService cacheService() {
+ return new GridSpringCacheTestService();
+ }
+
+ /** */
+ @Bean
+ public GridSpringDynamicCacheTestService dynamicCacheService() {
+ return new GridSpringDynamicCacheTestService();
+ }
+
+ /** */
+ @Bean
+ public IgniteClient igniteClient() {
+ return Ignition.startClient(new ClientConfiguration()
+ .setAddresses("127.0.0.1:" + DFLT_PORT)
+ .setBinaryConfiguration(new BinaryConfiguration().setCompactFooter(true)));
+ }
+
+ /** */
+ @Bean
+ public AbstractCacheManager cacheManager(IgniteClient cli) {
+ return new IgniteClientSpringCacheManager()
+ .setClientInstance(cli)
+ .setDynamicCacheConfiguration(new ClientCacheConfiguration().setBackups(2));
+ }
+
+ /** {@inheritDoc} */
+ @Override public KeyGenerator keyGenerator() {
+ return new GridSpringCacheTestKeyGenerator();
+ }
+ }
+
+ /** */
+ @Configuration
+ @EnableCaching
+ public static class ClientConfigurationApplicationContext {
+ /** */
+ @Bean
+ public AbstractCacheManager cacheManager() {
+ return new IgniteClientSpringCacheManager()
+ .setClientConfiguration(new ClientConfiguration().setAddresses("127.0.0.1:" + DFLT_PORT));
+ }
+ }
+
+ /** */
+ @Configuration
+ @EnableCaching
+ public static class InvalidConnectionConfigurationApplicationContext {
+ /** */
+ @Bean
+ public AbstractCacheManager cacheManager() {
+ return new IgniteClientSpringCacheManager();
+ }
+ }
+}
diff --git a/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/SpringCacheTest.java b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/SpringCacheTest.java
index 8710273..06298b5 100644
--- a/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/SpringCacheTest.java
+++ b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/SpringCacheTest.java
@@ -20,6 +20,7 @@
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.internal.util.typedef.G;
+import org.apache.ignite.springdata.proxy.IgniteCacheProxyImpl;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
@@ -58,7 +59,7 @@
cacheName = String.valueOf(System.currentTimeMillis());
nativeCache = ignite.getOrCreateCache(cacheName);
- springCache = new SpringCache(nativeCache, null);
+ springCache = new SpringCache(new IgniteCacheProxyImpl<>(nativeCache), null);
}
/** {@inheritDoc} */
diff --git a/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/ignite-client-spring-caching.xml b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/ignite-client-spring-caching.xml
new file mode 100644
index 0000000..c35c12f
--- /dev/null
+++ b/modules/spring-cache-ext/src/test/java/org/apache/ignite/cache/spring/ignite-client-spring-caching.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:cache="http://www.springframework.org/schema/cache"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
+ <bean id="cacheManager" class="org.apache.ignite.cache.spring.IgniteClientSpringCacheManager">
+ <property name="clientConfiguration">
+ <bean class="org.apache.ignite.configuration.ClientConfiguration">
+ <property name="addresses">
+ <list>
+ <value>127.0.0.1:10801</value>
+ </list>
+ </property>
+ </bean>
+ </property>
+
+ <property name="dynamicCacheConfiguration">
+ <bean class="org.apache.ignite.client.ClientCacheConfiguration">
+ <property name="backups" value="2"/>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="ignite" class="org.apache.ignite.IgniteSpringBean">
+ <property name="configuration">
+ <bean class="org.apache.ignite.configuration.IgniteConfiguration">
+ <property name="clientConnectorConfiguration">
+ <bean class="org.apache.ignite.configuration.ClientConnectorConfiguration">
+ <property name="host" value="127.0.0.1"/>
+ <property name="port" value="10801"/>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <cache:annotation-driven/>
+</beans>
diff --git a/modules/spring-cache-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringCacheTestSuite.java b/modules/spring-cache-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringCacheTestSuite.java
index 6d7a797..0c5dc9e 100644
--- a/modules/spring-cache-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringCacheTestSuite.java
+++ b/modules/spring-cache-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringCacheTestSuite.java
@@ -20,6 +20,7 @@
import org.apache.ignite.cache.spring.GridSpringCacheManagerMultiJvmSelfTest;
import org.apache.ignite.cache.spring.GridSpringCacheManagerSelfTest;
import org.apache.ignite.cache.spring.GridSpringCacheManagerSpringBeanSelfTest;
+import org.apache.ignite.cache.spring.IgniteClientSpringCacheManagerTest;
import org.apache.ignite.cache.spring.SpringCacheManagerContextInjectionTest;
import org.apache.ignite.cache.spring.SpringCacheTest;
import org.junit.runner.RunWith;
@@ -34,7 +35,8 @@
GridSpringCacheManagerSpringBeanSelfTest.class,
SpringCacheManagerContextInjectionTest.class,
SpringCacheTest.class,
- GridSpringCacheManagerMultiJvmSelfTest.class
+ GridSpringCacheManagerMultiJvmSelfTest.class,
+ IgniteClientSpringCacheManagerTest.class
})
public class IgniteSpringCacheTestSuite {
}
diff --git a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheClientProxy.java b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheClientProxy.java
index 10fef14..5d7be5c 100644
--- a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheClientProxy.java
+++ b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheClientProxy.java
@@ -99,4 +99,29 @@
@Override public @NotNull Iterator<Cache.Entry<K, V>> iterator() {
return cache.<Cache.Entry<K, V>>query(new ScanQuery<>()).getAll().iterator();
}
+
+ /** {@inheritDoc} */
+ @Override public String getName() {
+ return cache.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteCacheProxy<K, V> withSkipStore() {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override public V getAndPutIfAbsent(K key, V val) {
+ return cache.getAndPutIfAbsent(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void removeAll() {
+ cache.removeAll();
+ }
+
+ /** {@inheritDoc} */
+ @Override public ClientCache<K, V> delegate() {
+ return cache;
+ }
}
diff --git a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxy.java b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxy.java
index 3c5c4d2..530b086 100644
--- a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxy.java
+++ b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxy.java
@@ -108,4 +108,26 @@
* @return <tt>true</tt> if this map contains a mapping for the specified key.
*/
public boolean containsKey(K key);
+
+ /** @return The name of the cache. */
+ public String getName();
+
+ /** @return Cache with read-through write-through behavior disabled. */
+ public IgniteCacheProxy<K, V> withSkipStore();
+
+ /**
+ * Atomically associates the specified key with the given value if it is not already associated with a value.
+ *
+ * @param key Key with which the specified value is to be associated.
+ * @param val Value to be associated with the specified key.
+ * @return Value that is already associated with the specified key, or {@code null} if no value was associated
+ * with the specified key and a value was set.
+ */
+ public V getAndPutIfAbsent(K key, V val);
+
+ /** Removes all of the mappings from this cache. */
+ public void removeAll();
+
+ /** @return Cache instance to which the current proxy delegates operations. */
+ public Object delegate();
}
diff --git a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxyImpl.java b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxyImpl.java
index 7a4c716..2c96506 100644
--- a/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxyImpl.java
+++ b/modules/spring-data-commons/src/main/java/org/apache/ignite/springdata/proxy/IgniteCacheProxyImpl.java
@@ -100,8 +100,28 @@
return cache.<Cache.Entry<K, V>>query(new ScanQuery<>()).getAll().iterator();
}
+ /** {@inheritDoc} */
+ @Override public String getName() {
+ return cache.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteCacheProxy<K, V> withSkipStore() {
+ return new IgniteCacheProxyImpl<>(cache.withSkipStore());
+ }
+
+ /** {@inheritDoc} */
+ @Override public V getAndPutIfAbsent(K key, V val) {
+ return cache.getAndPutIfAbsent(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void removeAll() {
+ cache.removeAll();
+ }
+
/** @return {@link IgniteCache} instance to which operations are delegated. */
- public IgniteCache<K, V> delegate() {
+ @Override public IgniteCache<K, V> delegate() {
return cache;
}
}