SGF-528 - Enable GemfireCacheManager to explicitly specify Cache names referring to Regions that will be used in Spring's Caching Infrastructure.
(cherry picked from commit 1af7ad523ae701ccd3e4770695ce1dcc7b5e12ef) Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 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.data.gemfire.support;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
/**
|
||||
* Abstract base test class for native cache implementations.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author John Blum
|
||||
* @see org.springframework.cache.Cache
|
||||
*/
|
||||
public abstract class AbstractNativeCacheTest<T> {
|
||||
|
||||
protected static final String CACHE_NAME = "Example";
|
||||
|
||||
private T nativeCache;
|
||||
private Cache cache;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
nativeCache = createNativeCache();
|
||||
cache = createCache(nativeCache);
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <C extends Cache> C createCache() throws Exception {
|
||||
return (C) createCache(createNativeCache());
|
||||
}
|
||||
|
||||
protected abstract Cache createCache(T nativeCache);
|
||||
|
||||
protected abstract T createNativeCache() throws Exception;
|
||||
|
||||
@Test
|
||||
public void cacheNameIsEqualToExpected() throws Exception {
|
||||
assertThat(cache.getName(), is(equalTo(CACHE_NAME)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void nativeCacheIsSameAsExpected() throws Exception {
|
||||
assertThat((T) cache.getNativeCache(), is(sameInstance(nativeCache)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePutIsSuccessful() throws Exception {
|
||||
assertThat(cache.get("enescu"), is(nullValue()));
|
||||
cache.put("enescu", "george");
|
||||
assertThat(String.valueOf(cache.get("enescu").get()), is(equalTo("george")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheGetForClassTypeIsSuccessful() {
|
||||
cache.put("one", Boolean.TRUE);
|
||||
cache.put("two", 'X');
|
||||
cache.put("three", 101);
|
||||
cache.put("four", Math.PI);
|
||||
cache.put("five", "TEST");
|
||||
|
||||
assertThat(cache.get("one", Boolean.class), is(equalTo(Boolean.TRUE)));
|
||||
assertThat(cache.get("two", Character.class), is(equalTo('X')));
|
||||
assertThat(cache.get("three", Integer.class), is(equalTo(101)));
|
||||
assertThat(cache.get("four", Double.class), is(equalTo(Math.PI)));
|
||||
assertThat(cache.get("five", String.class), is(equalTo("TEST")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheClearIsSuccessful() throws Exception {
|
||||
assertThat(cache.get("enescu"), is(nullValue()));
|
||||
|
||||
cache.put("enescu", "george");
|
||||
|
||||
assertThat(cache.get("vlaicu"), is(nullValue()));
|
||||
|
||||
cache.put("vlaicu", "aurel");
|
||||
|
||||
assertThat(cache.get("enescu", String.class), is(equalTo("george")));
|
||||
assertThat(cache.get("vlaicu", String.class), is(equalTo("aurel")));
|
||||
|
||||
cache.clear();
|
||||
|
||||
assertThat(cache.get("vlaicu"), is(nullValue()));
|
||||
assertThat(cache.get("enescu"), is(nullValue()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2010 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.data.gemfire.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
/**
|
||||
* Abstract base test class for native cache implementations.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.cache.Cache
|
||||
*/
|
||||
public abstract class AbstractNativeCacheTests<T> {
|
||||
|
||||
protected static final String CACHE_NAME = "Example";
|
||||
|
||||
private T nativeCache;
|
||||
private Cache cache;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
nativeCache = newNativeCache();
|
||||
cache = newCache(nativeCache);
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <C extends Cache> C newCache() throws Exception {
|
||||
return (C) newCache(newNativeCache());
|
||||
}
|
||||
|
||||
protected abstract Cache newCache(T nativeCache);
|
||||
|
||||
protected abstract T newNativeCache() throws Exception;
|
||||
|
||||
@Test
|
||||
public void cacheNameIsEqualToExpected() throws Exception {
|
||||
assertThat(cache.getName()).isEqualTo(CACHE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeCacheIsSameAsExpected() throws Exception {
|
||||
assertThat(cache.getNativeCache()).isSameAs(nativeCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePutIsSuccessful() throws Exception {
|
||||
assertThat(cache.get("enescu")).isNull();
|
||||
|
||||
cache.put("enescu", "george");
|
||||
|
||||
assertThat(cache.get("enescu").get()).isEqualTo("george");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePutThenClearIsSuccessful() throws Exception {
|
||||
cache.put("enescu", "george");
|
||||
cache.put("vlaicu", "aurel");
|
||||
|
||||
assertThat(cache.get("enescu", String.class)).isEqualTo("george");
|
||||
assertThat(cache.get("vlaicu", String.class)).isEqualTo("aurel");
|
||||
|
||||
cache.clear();
|
||||
|
||||
assertThat(cache.get("vlaicu")).isNull();
|
||||
assertThat(cache.get("enescu")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePutThenGetForClassTypeIsSuccessful() {
|
||||
cache.put("one", Boolean.TRUE);
|
||||
cache.put("two", 'X');
|
||||
cache.put("three", 101);
|
||||
cache.put("four", Math.PI);
|
||||
cache.put("five", "TEST");
|
||||
|
||||
assertThat(cache.get("one", Boolean.class)).isTrue();
|
||||
assertThat(cache.get("two", Character.class)).isEqualTo('X');
|
||||
assertThat(cache.get("three", Integer.class)).isEqualTo(101);
|
||||
assertThat(cache.get("four", Double.class)).isEqualTo(Math.PI);
|
||||
assertThat(cache.get("five", String.class)).isEqualTo("TEST");
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
package org.springframework.data.gemfire.support;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import edu.umd.cs.mtc.MultithreadedTestCase;
|
||||
import edu.umd.cs.mtc.TestFramework;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheFactory;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -30,42 +35,35 @@ import org.junit.rules.ExpectedException;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.data.gemfire.GemfireUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheFactory;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
import edu.umd.cs.mtc.MultithreadedTestCase;
|
||||
import edu.umd.cs.mtc.TestFramework;
|
||||
|
||||
/**
|
||||
* Tests the interaction between the Spring Framework Cache Abstraction and GemFire as a provider
|
||||
* using Spring Data GemFire's extension.
|
||||
* Integration Tests for {@link GemfireCache}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author John Blum
|
||||
* @author Oliver Gierke
|
||||
* @see org.junit.Rule
|
||||
* @see org.junit.Test
|
||||
* @see edu.umd.cs.mtc.MultithreadedTestCase
|
||||
* @see edu.umd.cs.mtc.TestFramework
|
||||
* @see org.springframework.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
*/
|
||||
public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Object>> {
|
||||
public class GemfireCacheIntegrationTests extends AbstractNativeCacheTests<Region<Object, Object>> {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Override
|
||||
protected Cache createCache(Region<Object, Object> nativeCache) {
|
||||
protected Cache newCache(Region<Object, Object> nativeCache) {
|
||||
return new GemfireCache(nativeCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region<Object, Object> createNativeCache() throws Exception {
|
||||
protected Region<Object, Object> newNativeCache() throws Exception {
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", GemfireCacheTest.class.getName());
|
||||
gemfireProperties.setProperty("name", GemfireCacheIntegrationTests.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("locators", "");
|
||||
gemfireProperties.setProperty("log-level", "warning");
|
||||
|
||||
com.gemstone.gemfire.cache.Cache cache = GemfireUtils.getCache();
|
||||
@@ -84,11 +82,11 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
*/
|
||||
@Test
|
||||
public void findsTypedValue() throws Exception {
|
||||
Cache cache = createCache();
|
||||
Cache cache = newCache();
|
||||
|
||||
cache.put("key", "value");
|
||||
|
||||
assertThat(cache.get("key", String.class), is("value"));
|
||||
assertThat(cache.get("key", String.class)).isEqualTo("value");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,11 +94,11 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
*/
|
||||
@Test
|
||||
public void skipTypeChecksIfTargetTypeIsNull() throws Exception {
|
||||
Cache cache = createCache();
|
||||
Cache cache = newCache();
|
||||
|
||||
cache.put("key", "value");
|
||||
cache.put("key", 1);
|
||||
|
||||
assertThat(cache.get("key", (Class<String>) null), is("value"));
|
||||
assertThat(cache.get("key", (Class<?>) null)).isEqualTo(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,63 +106,67 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
*/
|
||||
@Test
|
||||
public void throwsIllegalStateExceptionIfTypedAccessDoesNotFindMatchingType() throws Exception {
|
||||
Cache cache = createCache();
|
||||
Cache cache = newCache();
|
||||
|
||||
cache.put("key", "value");
|
||||
|
||||
expectedException.expect(IllegalStateException.class);
|
||||
expectedException.expectMessage(Integer.class.getName());
|
||||
expectedException.expectMessage("value");
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectMessage(Integer.class.getName());
|
||||
exception.expectMessage(String.format("Cached value [value] is not an instance of type [%s]",
|
||||
Integer.class.getName()));
|
||||
|
||||
cache.get("key", Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheGetWithValueLoaderFindsValue() throws Exception {
|
||||
GemfireCache cache = createCache();
|
||||
GemfireCache cache = newCache();
|
||||
|
||||
cache.put("key", "value");
|
||||
|
||||
assertThat(String.valueOf(cache.get("key", TestCacheLoader.NULL_VALUE)), is(equalTo("value")));
|
||||
assertThat(TestCacheLoader.NULL_VALUE.wasCalled(), is(false));
|
||||
assertThat(cache.get("key", TestValueLoader.NULL_VALUE)).isEqualTo("value");
|
||||
assertThat(TestValueLoader.NULL_VALUE.wasCalled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cacheGetWithValueLoaderUsesValueLoader() throws Exception {
|
||||
GemfireCache cache = createCache();
|
||||
public void cacheGetWithValueLoaderUsesValueLoaderReturnsValue() throws Exception {
|
||||
GemfireCache cache = newCache();
|
||||
|
||||
TestCacheLoader<String> cacheLoader = new TestCacheLoader<String>("test");
|
||||
TestValueLoader<String> valueLoader = new TestValueLoader<String>("test");
|
||||
|
||||
assertThat(cache.get("key", cacheLoader), is(equalTo("test")));
|
||||
assertThat(cacheLoader.wasCalled(), is(true));
|
||||
assertThat(((Region<Object, String>) cache.getNativeCache()).get("key"), is(equalTo("test")));
|
||||
assertThat(cache.get("key", valueLoader)).isEqualTo("test");
|
||||
assertThat(valueLoader.wasCalled()).isTrue();
|
||||
assertThat(((Region<Object, String>) cache.getNativeCache()).get("key")).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cacheGetWithValueLoaderUsesValueLoaderReturningNull() throws Exception {
|
||||
GemfireCache cache = createCache();
|
||||
public void cacheGetWithValueLoaderUsesValueLoaderReturnsNull() throws Exception {
|
||||
GemfireCache cache = newCache();
|
||||
|
||||
assertThat(cache.get("key", TestCacheLoader.NULL_VALUE), is(nullValue()));
|
||||
assertThat(TestCacheLoader.NULL_VALUE.wasCalled(), is(true));
|
||||
assertThat(cache.getNativeCache().containsKey("key"), is(false));
|
||||
assertThat(cache.get("key", TestValueLoader.NULL_VALUE)).isNull();
|
||||
assertThat(TestValueLoader.NULL_VALUE.wasCalled()).isTrue();
|
||||
assertThat(cache.getNativeCache().containsKey("key")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void cacheGetWithValueLoaderUsesValueLoaderThrowingException() throws Exception {
|
||||
GemfireCache cache = createCache();
|
||||
public void cacheGetWithValueLoaderUsesValueLoaderAndThrowsException() throws Exception {
|
||||
GemfireCache cache = newCache();
|
||||
|
||||
TestCacheLoader<Exception> exceptionThrowingCacheLoader = new TestCacheLoader<Exception>(
|
||||
new IllegalStateException("test"));
|
||||
try {
|
||||
TestValueLoader<Exception> exceptionThrowingValueLoader = new TestValueLoader<Exception>(
|
||||
new IllegalStateException("test"));
|
||||
|
||||
expectedException.expect(RuntimeException.class);
|
||||
expectedException.expectCause(is(nullValue(IllegalStateException.class)));
|
||||
expectedException.expectMessage(String.format("Failed to load value for key [key] using valueLoader [%1$s]",
|
||||
exceptionThrowingCacheLoader.getClass().getName()));
|
||||
exception.expect(Cache.ValueRetrievalException.class);
|
||||
exception.expectCause(is(IllegalStateException.class));
|
||||
|
||||
cache.get("key", exceptionThrowingCacheLoader);
|
||||
cache.get("key", exceptionThrowingValueLoader);
|
||||
}
|
||||
finally {
|
||||
assertThat(cache.getNativeCache().containsKey("key")).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -177,15 +179,15 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
|
||||
private GemfireCache cache;
|
||||
|
||||
private TestCacheLoader<String> cacheLoader;
|
||||
private TestValueLoader<String> cacheLoader;
|
||||
|
||||
@Override
|
||||
public void initialize(){
|
||||
super.initialize();
|
||||
|
||||
cache = createCacheHandlesCheckedException();
|
||||
cache = newCacheHandlesCheckedException();
|
||||
|
||||
cacheLoader = new TestCacheLoader<String>("test") {
|
||||
cacheLoader = new TestValueLoader<String>("test") {
|
||||
@Override public String call() throws Exception {
|
||||
waitForTick(2);
|
||||
return super.call();
|
||||
@@ -193,12 +195,12 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
};
|
||||
}
|
||||
|
||||
<T extends Cache> T createCacheHandlesCheckedException() {
|
||||
<T extends Cache> T newCacheHandlesCheckedException() {
|
||||
try {
|
||||
return createCache();
|
||||
return newCache();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("failed to create Cache", e);
|
||||
throw new RuntimeException("Failed to create Cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +212,8 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
String value = cache.get("key", cacheLoader);
|
||||
|
||||
assertTick(2);
|
||||
assertThat(value, is(equalTo("test")));
|
||||
assertThat(cacheLoader.wasCalled(), is(true));
|
||||
assertThat(value).isEqualTo("test");
|
||||
assertThat(cacheLoader.wasCalled()).isTrue();
|
||||
}
|
||||
|
||||
public void thread2() {
|
||||
@@ -219,41 +221,39 @@ public class GemfireCacheTest extends AbstractNativeCacheTest<Region<Object, Obj
|
||||
|
||||
Thread.currentThread().setName("Cache Reader Thread");
|
||||
|
||||
TestCacheLoader<String> illegalCacheLoader = new TestCacheLoader<String>("illegal");
|
||||
TestValueLoader<String> illegalCacheLoader = new TestValueLoader<String>("illegal");
|
||||
|
||||
String value = cache.get("key", illegalCacheLoader);
|
||||
|
||||
assertTick(2);
|
||||
assertThat(value, is(equalTo("test")));
|
||||
assertThat(illegalCacheLoader.wasCalled(), is(false));
|
||||
assertThat(value).isEqualTo("test");
|
||||
assertThat(illegalCacheLoader.wasCalled()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class TestCacheLoader<T> implements Callable<T> {
|
||||
protected static class TestValueLoader<T> implements Callable<T> {
|
||||
|
||||
protected static final TestCacheLoader<Object> NULL_VALUE = new TestCacheLoader<Object>();
|
||||
protected static final TestValueLoader<Object> NULL_VALUE = new TestValueLoader<Object>();
|
||||
|
||||
private volatile boolean called;
|
||||
private AtomicBoolean called = new AtomicBoolean(false);
|
||||
|
||||
private final T value;
|
||||
|
||||
public TestCacheLoader() {
|
||||
public TestValueLoader() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public TestCacheLoader(T value) {
|
||||
public TestValueLoader(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
protected boolean wasCalled() {
|
||||
boolean called = this.called;
|
||||
this.called = false;
|
||||
return called;
|
||||
return called.compareAndSet(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T call() throws Exception {
|
||||
called = true;
|
||||
called.set(true);
|
||||
|
||||
if (value instanceof Exception) {
|
||||
throw (Exception) value;
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2012 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.data.gemfire.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GemfireCacheManager}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @since 1.9.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GemfireCacheManagerUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private GemFireCache mockGemFireCache;
|
||||
|
||||
private GemfireCacheManager cacheManager;
|
||||
|
||||
@Mock
|
||||
private Region mockRegion;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
cacheManager = new GemfireCacheManager();
|
||||
}
|
||||
|
||||
protected <T> Set<T> asSet(T... elements) {
|
||||
Set<T> set = new HashSet<T>(elements.length);
|
||||
Collections.addAll(set, elements);
|
||||
return set;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Region<?, ?> mockRegion(String name) {
|
||||
Region<?, ?> mockRegion = mock(Region.class, name);
|
||||
when(mockRegion.getName()).thenReturn(name);
|
||||
return mockRegion;
|
||||
}
|
||||
|
||||
protected Region<?, ?> regionFor(Iterable<Region<?, ?>> regions, String name) {
|
||||
for (Region<?, ?> region : regions) {
|
||||
if (region.getName().equals(name)) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireCacheAvailableWithAvailableGemFireCacheIsSuccessful() {
|
||||
when(mockGemFireCache.isClosed()).thenReturn(false);
|
||||
assertThat(cacheManager.assertGemFireCacheAvailable(mockGemFireCache)).isSameAs(mockGemFireCache);
|
||||
verify(mockGemFireCache, times(1)).isClosed();
|
||||
verify(mockGemFireCache, times(1)).getName();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireCacheAvailableWithNullThrowsIllegalStateException() {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("A GemFire cache instance is required")));
|
||||
|
||||
cacheManager.assertGemFireCacheAvailable(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireCacheAvailableWithNamedClosedGemFireCacheThrowsIllegalStateException() {
|
||||
when(mockGemFireCache.isClosed()).thenReturn(true);
|
||||
when(mockGemFireCache.getName()).thenReturn("Example");
|
||||
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("GemFire cache [Example] has been closed")));
|
||||
|
||||
cacheManager.assertGemFireCacheAvailable(mockGemFireCache);
|
||||
}
|
||||
finally {
|
||||
verify(mockGemFireCache,times(1)).isClosed();
|
||||
verify(mockGemFireCache, times(1)).getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireRegionAvailableWithAvailableGemFireRegionIsSuccessful() {
|
||||
when(mockRegion.isDestroyed()).thenReturn(false);
|
||||
assertThat(cacheManager.assertGemFireRegionAvailable(mockRegion, "Example")).isSameAs(mockRegion);
|
||||
verify(mockRegion, times(1)).isDestroyed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireRegionAvailableWithNullThrowIllegalStateException() {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("No Region for cache name [Example] was found")));
|
||||
|
||||
cacheManager.assertGemFireRegionAvailable(null, "Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertGemFireRegionAvailableWithDestroyedGemFireRegionThrowIllegalStateException() {
|
||||
when(mockRegion.isDestroyed()).thenReturn(true);
|
||||
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("Region [Example] has been destroyed")));
|
||||
|
||||
cacheManager.assertGemFireRegionAvailable(mockRegion, "Example");
|
||||
}
|
||||
finally {
|
||||
verify(mockRegion, times(1)).isDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadCachesIsSuccessful() {
|
||||
Set<Region<?, ?>> regions = asSet(mockRegion("one"), mockRegion("two"), mockRegion("three"));
|
||||
|
||||
cacheManager.setRegions(regions);
|
||||
|
||||
Collection<Cache> loadedCaches = cacheManager.loadCaches();
|
||||
|
||||
assertThat(loadedCaches).isNotNull();
|
||||
assertThat(loadedCaches.size()).isEqualTo(3);
|
||||
|
||||
for (Cache cache : loadedCaches) {
|
||||
assertThat(cache).isInstanceOf(GemfireCache.class);
|
||||
assertThat(regionFor(regions, cache.getName())).isSameAs(cache.getNativeCache());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveRegionsReturnsGivenRegions() {
|
||||
Set<Region<?, ?>> regions = asSet(mockRegion("one"), mockRegion("two"));
|
||||
|
||||
assertThat(cacheManager.resolveRegions(mockGemFireCache, regions, asSet("three", "four"))).isSameAs(regions);
|
||||
assertThat(cacheManager.isDynamic()).isFalse();
|
||||
|
||||
verify(mockGemFireCache, never()).getRegion(anyString());
|
||||
verify(mockGemFireCache, never()).rootRegions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void resolveRegionsReturnsRegionsForCacheNamesOnly() {
|
||||
Region mockRegionOne = mockRegion("one");
|
||||
Region mockRegionTwo = mockRegion("two");
|
||||
Region mockRegionThree = mockRegion("three");
|
||||
|
||||
when(mockGemFireCache.getRegion(eq("one"))).thenReturn(mockRegionOne);
|
||||
when(mockGemFireCache.getRegion(eq("two"))).thenReturn(mockRegionTwo);
|
||||
when(mockGemFireCache.getRegion(eq("three"))).thenReturn(mockRegionThree);
|
||||
|
||||
Set<Region<?, ?>> regions = cacheManager.resolveRegions(mockGemFireCache, null, asSet("one", "two"));
|
||||
|
||||
assertThat(regions).isNotNull();
|
||||
assertThat(regions.size()).isEqualTo(2);
|
||||
assertThat(regions).containsAll(this.<Region<?, ?>>asSet(mockRegionOne, mockRegionTwo));
|
||||
assertThat(cacheManager.isDynamic()).isFalse();
|
||||
|
||||
verify(mockGemFireCache, times(1)).getRegion(eq("one"));
|
||||
verify(mockGemFireCache, times(1)).getRegion(eq("two"));
|
||||
verify(mockGemFireCache, never()).getRegion(eq("three"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveRegionsReturnsGemFireCacheRootRegions() {
|
||||
Set<Region<?, ?>> rootRegions = asSet(mockRegion("one"), mockRegion("two"));
|
||||
|
||||
when(mockGemFireCache.rootRegions()).thenReturn(rootRegions);
|
||||
|
||||
assertThat(cacheManager.resolveRegions(mockGemFireCache, null, null)).isSameAs(rootRegions);
|
||||
assertThat(cacheManager.isDynamic()).isTrue();
|
||||
|
||||
verify(mockGemFireCache, times(1)).rootRegions();
|
||||
verify(mockGemFireCache, never()).getRegion(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSetForNonNullNonEmptyIterableIsTrue() {
|
||||
assertThat(cacheManager.isSet(Collections.singleton(1))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSetForNullIterableIsFalse() {
|
||||
assertThat(cacheManager.isSet(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSetForEmptyIterableIsFalse() {
|
||||
assertThat(cacheManager.isSet(Collections.emptyList())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void regionForCacheNameReturnsRegion() {
|
||||
when(mockGemFireCache.isClosed()).thenReturn(false);
|
||||
when(mockGemFireCache.getName()).thenReturn("regionForCacheNameReturnsRegion");
|
||||
when(mockGemFireCache.getRegion(eq("Example"))).thenReturn(mockRegion);
|
||||
when(mockRegion.isDestroyed()).thenReturn(false);
|
||||
|
||||
assertThat(cacheManager.regionFor(mockGemFireCache, "Example")).isSameAs(mockRegion);
|
||||
|
||||
verify(mockGemFireCache, times(1)).isClosed();
|
||||
verify(mockGemFireCache, times(1)).getName();
|
||||
verify(mockGemFireCache, times(1)).getRegion(eq("Example"));
|
||||
verify(mockRegion, times(1)).isDestroyed();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getMissingCacheReturnsMissingCache() {
|
||||
Region mockRegion = mockRegion("missing");
|
||||
|
||||
when(mockGemFireCache.getRegion(eq("missing"))).thenReturn(mockRegion);
|
||||
|
||||
cacheManager.setCache(mockGemFireCache);
|
||||
|
||||
Cache cache = cacheManager.getMissingCache("missing");
|
||||
|
||||
assertThat(cache).isNotNull();
|
||||
assertThat(cache.getNativeCache()).isEqualTo(mockRegion);
|
||||
|
||||
verify(mockGemFireCache, times(1)).getRegion(eq("missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMissingCacheReturnsNull() {
|
||||
cacheManager.setRegions(Collections.<Region<?, ?>>singleton(mockRegion("one")));
|
||||
cacheManager.afterPropertiesSet();
|
||||
|
||||
assertThat(cacheManager.isDynamic()).isFalse();
|
||||
assertThat(cacheManager.getMissingCache("missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetCache() {
|
||||
assertThat(cacheManager.getCache()).isNull();
|
||||
|
||||
cacheManager.setCache(mockGemFireCache);
|
||||
|
||||
assertThat(cacheManager.getCache()).isSameAs(mockGemFireCache);
|
||||
|
||||
cacheManager.setCache(null);
|
||||
|
||||
assertThat(cacheManager.getCache()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetCacheNames() {
|
||||
Set<Region<?, ?>> regions = asSet(mockRegion("one"), mockRegion("two"));
|
||||
|
||||
cacheManager.setRegions(regions);
|
||||
cacheManager.afterPropertiesSet();
|
||||
|
||||
assertThat(cacheManager.getCacheNames()).containsAll(asSet("one", "two"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetRegions() {
|
||||
Set<Region<?, ?>> regions = asSet(mockRegion("one"), mockRegion("two"));
|
||||
|
||||
assertThat(cacheManager.getRegions()).isNull();
|
||||
|
||||
cacheManager.setRegions(regions);
|
||||
|
||||
assertThat(cacheManager.getRegions()).isSameAs(regions);
|
||||
|
||||
cacheManager.setRegions(null);
|
||||
|
||||
assertThat(cacheManager.getRegions()).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2012 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.data.gemfire.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.isA;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GemfireCache}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @see org.springframework.data.gemfire.support.GemfireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @since 1.9.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GemfireCacheUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private Callable mockCallable;
|
||||
|
||||
@Mock
|
||||
private Region mockRegion;
|
||||
|
||||
@Test
|
||||
public void wrapIsSuccessful() {
|
||||
GemfireCache gemfireCache = GemfireCache.wrap(mockRegion);
|
||||
|
||||
assertThat(gemfireCache).isNotNull();
|
||||
assertThat(gemfireCache.getNativeCache()).isEqualTo(mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructGemfireCacheWithNullRegion() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("GemFire Region must not be null")));
|
||||
|
||||
new GemfireCache(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNameReturnsRegionName() {
|
||||
when(mockRegion.getName()).thenReturn("Example");
|
||||
assertThat(GemfireCache.wrap(mockRegion).getName()).isEqualTo("Example");
|
||||
verify(mockRegion, times(1)).getName();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCallsRegionClear() {
|
||||
GemfireCache.wrap(mockRegion).clear();
|
||||
verify(mockRegion, times(1)).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evictCallsRegionDestoryWithKey() {
|
||||
GemfireCache.wrap(mockRegion).evict("key");
|
||||
verify(mockRegion, times(1)).destroy(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueWrapperForKey() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn("test");
|
||||
|
||||
Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).get("key");
|
||||
|
||||
assertThat(value).isNotNull();
|
||||
assertThat(value.get()).isEqualTo("test");
|
||||
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsNullForKey() {
|
||||
when(mockRegion.get(anyString())).thenReturn(null);
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key")).isNull();
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueForKeyAsDesiredType() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn(1);
|
||||
|
||||
Object value = GemfireCache.wrap(mockRegion).get("key", Integer.class);
|
||||
|
||||
assertThat(value).isNotNull();
|
||||
assertThat(value).isInstanceOf(Integer.class);
|
||||
assertThat(value).isEqualTo(1);
|
||||
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsNullForKeyAsDesiredType() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn(null);
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key", Double.class)).isNull();
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueForKeyWithNullDesiredType() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn(true);
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key", (Class<Boolean>) null)).isTrue();
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getThrowsIllegalStateExceptionForKeyWhenValueIsNotAnInstanceOfDesiredType() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn(1);
|
||||
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(String.format("Cached value [1] is not an instance of type [%s]",
|
||||
Boolean.class.getName()));
|
||||
|
||||
GemfireCache.wrap(mockRegion).get("key", Boolean.class);
|
||||
}
|
||||
finally {
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueFromCacheForKeyWithValueLoader() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn("test");
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("test");
|
||||
verify(mockRegion, times(1)).get(eq("key"));
|
||||
verifyZeroInteractions(mockCallable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueFromCacheForKeyAfterSynchronizationWithValueLoader() {
|
||||
when(mockRegion.get(eq("key"))).thenReturn(null).thenReturn("test");
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("test");
|
||||
verify(mockRegion, times(2)).get(eq("key"));
|
||||
verifyZeroInteractions(mockCallable);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReturnsValueFromValueLoaderForKeyWithValueLoader() throws Exception {
|
||||
when(mockRegion.get(anyString())).thenReturn(null);
|
||||
when(mockCallable.call()).thenReturn("mockValue");
|
||||
assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("mockValue");
|
||||
verify(mockRegion, times(2)).get(eq("key"));
|
||||
verify(mockCallable, times(1)).call();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getThrowsValueRetrievalExceptionForKeyWithValueLoader() throws Exception {
|
||||
when(mockRegion.get(anyString())).thenReturn(null);
|
||||
when(mockCallable.call()).thenThrow(new IllegalStateException("test"));
|
||||
|
||||
try {
|
||||
exception.expect(Cache.ValueRetrievalException.class);
|
||||
exception.expectCause(isA(IllegalStateException.class));
|
||||
|
||||
GemfireCache.wrap(mockRegion).get("key", mockCallable);
|
||||
}
|
||||
finally {
|
||||
verify(mockRegion, times(2)).get(eq("key"));
|
||||
verify(mockCallable, times(1)).call();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void putCachesValue() {
|
||||
GemfireCache.wrap(mockRegion).put("key", "test");
|
||||
verify(mockRegion, times(1)).put(eq("key"), eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void putDoesNotCacheNull() {
|
||||
GemfireCache.wrap(mockRegion).put("key", null);
|
||||
verify(mockRegion, never()).put(anyString(), anyObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void putIfAbsentReturnsExistingValue() {
|
||||
when(mockRegion.putIfAbsent(eq("key"), anyObject())).thenReturn("test");
|
||||
|
||||
Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).putIfAbsent("key", "mockValue");
|
||||
|
||||
assertThat(value).isNotNull();
|
||||
assertThat(value.get()).isEqualTo("test");
|
||||
|
||||
verify(mockRegion, times(1)).putIfAbsent(eq("key"), eq("mockValue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void putIfAbsentReturnsNull() {
|
||||
when(mockRegion.putIfAbsent(eq("key"), anyObject())).thenReturn(null);
|
||||
|
||||
Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).putIfAbsent("key", "mockValue");
|
||||
|
||||
assertThat(value).isNull();
|
||||
|
||||
verify(mockRegion, times(1)).putIfAbsent(eq("key"), eq("mockValue"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user