Skip to content

Commit

Permalink
Hacking
Browse files Browse the repository at this point in the history
  • Loading branch information
snicoll committed Dec 21, 2023
1 parent 85cb6cc commit cb4d6c5
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ private Object executeSynchronized(CacheOperationInvoker invoker, Method method,
}
}
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
return wrapCacheValue(method, doGetSync(invoker, cache, key));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
Expand Down Expand Up @@ -576,6 +576,23 @@ private Object evaluate(@Nullable Object cacheHit, CacheOperationInvoker invoker
return returnValue;
}

@Nullable
private Object doGetSync(CacheOperationInvoker invoker, Cache cache, Object key) {
try {
return cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker)));
}
catch (RuntimeException ex) {
// Make sure method invocation exceptions are not managed by the error handler
if (ex instanceof Cache.ValueRetrievalException) {
throw ex;
}
getErrorHandler().handleCacheGetError(ex, cache, key);
// If the exception is handled, we're just invoking the method and ignore
// the cache infrastructure failure
return unwrapReturnValue(invokeOperation(invoker));
}
}

@Nullable
private Object unwrapCacheValue(@Nullable Object cacheValue) {
return (cacheValue instanceof Cache.ValueWrapper wrapper ? wrapper.get() : cacheValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package org.springframework.cache.annotation;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Flux;
Expand All @@ -29,12 +31,17 @@
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
* Tests for annotation-based caching methods that use reactive operators.
Expand Down Expand Up @@ -111,6 +118,19 @@ void cacheHitDetermination(Class<?> configClass) {
ctx.close();
}

@Test
void cacheErrorHandlerIsInvoked() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
CacheErrorHandlerConfig.class, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
CacheErrorHandler errorHandler = ctx.getBean(CacheErrorHandler.class);

Object key = new Object();
Long r1 = service.cacheFuture(key).join();
verify(errorHandler).handleCacheGetError(any(), any(), key);

}


@CacheConfig(cacheNames = "first")
static class ReactiveCacheableService {
Expand Down Expand Up @@ -210,4 +230,31 @@ public void put(Object key, @Nullable Object value) {
}
}

@Configuration(proxyBeanMethods = false)
@EnableCaching
static class CacheErrorHandlerConfig implements CachingConfigurer {

@Bean
@Override
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("first") {
@Override
public Cache getCache(String name) {
Cache cache = mock(Cache.class);
given(cache.getName()).willReturn(name);
given(cache.retrieve(any())).willThrow(new IllegalStateException("Expected backend failure"));
given(cache.get(any(), any(Callable.class))).willThrow(new IllegalStateException("Expecteed backend failure"));
return cache;
}
};
}

@Bean
@Override
public CacheErrorHandler errorHandler() {
return mock(CacheErrorHandler.class);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
package org.springframework.cache.interceptor;

import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
Expand All @@ -39,6 +43,8 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.BDDMockito.willThrow;
Expand Down Expand Up @@ -88,6 +94,51 @@ void getFail() {
verify(this.cache).put(0L, result); // result of the invocation
}

@Test
public void getSyncFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
willThrow(exception).given(this.cache).get(eq(0L), any(Callable.class));

Object result = this.simpleService.getSync(0L);
assertThat(result).isEqualTo(0L);
verify(this.errorHandler).handleCacheGetError(exception, cache, 0L);
verify(this.cache).get(eq(0L), any(Callable.class));
}

@Test
public void getCompletableFutureFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
willThrow(exception).given(this.cache).retrieve(eq(0L));

Object result = this.simpleService.getFuture(0L).join();
assertThat(result).isEqualTo(0L);
verify(this.errorHandler).handleCacheGetError(exception, cache, 0L);
verify(this.cache).retrieve(eq(0L));
}

@Test
public void getMonoFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
willThrow(exception).given(this.cache).retrieve(eq(0L));

Object result = this.simpleService.getMono(0L).block();
assertThat(result).isEqualTo(0L);
verify(this.errorHandler).handleCacheGetError(exception, cache, 0L);
verify(this.cache).retrieve(eq(0L));
}


@Test
public void getFluxFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
willThrow(exception).given(this.cache).retrieve(eq(0L));

Object result = this.simpleService.getFlux(0L).blockLast();
assertThat(result).isEqualTo(0L);
verify(this.errorHandler).handleCacheGetError(exception, cache, 0L);
verify(this.cache).retrieve(eq(0L));
}

@Test
void getAndPutFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
Expand Down Expand Up @@ -220,6 +271,26 @@ public Object get(long id) {
return this.counter.getAndIncrement();
}

@Cacheable(sync = true)
public Object getSync(long id) {
return this.counter.getAndIncrement();
}

@Cacheable
public CompletableFuture<Long> getFuture(long id) {
return CompletableFuture.completedFuture(this.counter.getAndIncrement());
}

@Cacheable
public Mono<Long> getMono(long id) {
return Mono.just(this.counter.getAndIncrement());
}

@Cacheable
public Flux<Long> getFlux(long id) {
return Flux.just(this.counter.getAndIncrement(), 0L);
}

@CachePut
public Object put(long id) {
return this.counter.getAndIncrement();
Expand Down

0 comments on commit cb4d6c5

Please sign in to comment.