Commit c1ad9b73 authored by Stephane Nicoll's avatar Stephane Nicoll

Allow caching for an Endpoint operation with optional arguments

This commit makes sure that caching is enabled if an operation has
nullable parameters and the actual invocation provides null values.

Closes gh-11795
parent de11fa62
...@@ -20,6 +20,7 @@ import java.util.Map; ...@@ -20,6 +20,7 @@ import java.util.Map;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* An {@link OperationInvoker} that caches the response of an operation with a * An {@link OperationInvoker} that caches the response of an operation with a
...@@ -58,6 +59,9 @@ public class CachingOperationInvoker implements OperationInvoker { ...@@ -58,6 +59,9 @@ public class CachingOperationInvoker implements OperationInvoker {
@Override @Override
public Object invoke(Map<String, Object> arguments) { public Object invoke(Map<String, Object> arguments) {
if (hasArgument(arguments)) {
return this.invoker.invoke(arguments);
}
long accessTime = System.currentTimeMillis(); long accessTime = System.currentTimeMillis();
CachedResponse cached = this.cachedResponse; CachedResponse cached = this.cachedResponse;
if (cached == null || cached.isStale(accessTime, this.timeToLive)) { if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
...@@ -68,6 +72,17 @@ public class CachingOperationInvoker implements OperationInvoker { ...@@ -68,6 +72,17 @@ public class CachingOperationInvoker implements OperationInvoker {
return cached.getResponse(); return cached.getResponse();
} }
private boolean hasArgument(Map<String, Object> arguments) {
if (!ObjectUtils.isEmpty(arguments)) {
for (Object value : arguments.values()) {
if (value != null) {
return true;
}
}
}
return false;
}
/** /**
* Apply caching configuration when appropriate to the given invoker. * Apply caching configuration when appropriate to the given invoker.
* @param invoker the invoker to wrap * @param invoker the invoker to wrap
......
...@@ -21,6 +21,7 @@ import java.util.function.Function; ...@@ -21,6 +21,7 @@ import java.util.function.Function;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
/** /**
...@@ -40,7 +41,7 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor { ...@@ -40,7 +41,7 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
@Override @Override
public OperationInvoker apply(String endpointId, OperationType operationType, public OperationInvoker apply(String endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker) { OperationParameters parameters, OperationInvoker invoker) {
if (operationType == OperationType.READ && !parameters.hasParameters()) { if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) {
Long timeToLive = this.endpointIdTimeToLive.apply(endpointId); Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
if (timeToLive != null && timeToLive > 0) { if (timeToLive != null && timeToLive > 0) {
return new CachingOperationInvoker(invoker, timeToLive); return new CachingOperationInvoker(invoker, timeToLive);
...@@ -49,4 +50,13 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor { ...@@ -49,4 +50,13 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
return invoker; return invoker;
} }
private boolean hasMandatoryParameter(OperationParameters parameters) {
for (OperationParameter parameter : parameters) {
if (!parameter.isNullable()) {
return true;
}
}
return false;
}
} }
...@@ -28,6 +28,7 @@ import org.springframework.boot.actuate.endpoint.OperationType; ...@@ -28,6 +28,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod; import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import org.springframework.lang.Nullable;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
...@@ -40,6 +41,7 @@ import static org.mockito.Mockito.verify; ...@@ -40,6 +41,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link CachingOperationInvokerAdvisor}. * Tests for {@link CachingOperationInvokerAdvisor}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
public class CachingOperationInvokerAdvisorTests { public class CachingOperationInvokerAdvisorTests {
...@@ -66,8 +68,9 @@ public class CachingOperationInvokerAdvisorTests { ...@@ -66,8 +68,9 @@ public class CachingOperationInvokerAdvisorTests {
} }
@Test @Test
public void applyWhenHasParametersShouldNotAddAdvise() { public void applyWhenHasAtLeaseOneMandatoryParameterShouldNotAddAdvise() {
OperationParameters parameters = getParameters("getWithParameter", String.class); OperationParameters parameters = getParameters("getWithParameter", String.class,
String.class);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker); parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker); assertThat(advised).isSameAs(this.invoker);
...@@ -97,6 +100,18 @@ public class CachingOperationInvokerAdvisorTests { ...@@ -97,6 +100,18 @@ public class CachingOperationInvokerAdvisorTests {
public void applyShouldAddCacheAdvise() { public void applyShouldAddCacheAdvise() {
OperationParameters parameters = getParameters("get"); OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(100L); given(this.timeToLive.apply(any())).willReturn(100L);
assertAdviseIsApplied(parameters);
}
@Test
public void applyWithAllOptionalParameterShouldAddAdvise() {
OperationParameters parameters = getParameters("getWithAllOptionalParameters",
String.class, String.class);
given(this.timeToLive.apply(any())).willReturn(100L);
assertAdviseIsApplied(parameters);
}
private void assertAdviseIsApplied(OperationParameters parameters) {
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker); parameters, this.invoker);
assertThat(advised).isInstanceOf(CachingOperationInvoker.class); assertThat(advised).isInstanceOf(CachingOperationInvoker.class);
...@@ -123,7 +138,12 @@ public class CachingOperationInvokerAdvisorTests { ...@@ -123,7 +138,12 @@ public class CachingOperationInvokerAdvisorTests {
return ""; return "";
} }
public String getWithParameter(String foo) { public String getWithParameter(@Nullable String foo, String bar) {
return "";
}
public String getWithAllOptionalParameters(@Nullable String foo,
@Nullable String bar) {
return ""; return "";
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.invoker.cache; package org.springframework.boot.actuate.endpoint.invoker.cache;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -50,10 +51,21 @@ public class CachingOperationInvokerTests { ...@@ -50,10 +51,21 @@ public class CachingOperationInvokerTests {
} }
@Test @Test
public void cacheInTtlRange() { public void cacheInTtlRangeWithNoParameter() {
Object expected = new Object(); assertCacheIsUsed(Collections.emptyMap());
OperationInvoker target = mock(OperationInvoker.class); }
@Test
public void cacheInTtlWithNullParameters() {
Map<String, Object> parameters = new HashMap<>(); Map<String, Object> parameters = new HashMap<>();
parameters.put("first", null);
parameters.put("second", null);
assertCacheIsUsed(parameters);
}
private void assertCacheIsUsed(Map<String, Object> parameters) {
OperationInvoker target = mock(OperationInvoker.class);
Object expected = new Object();
given(target.invoke(parameters)).willReturn(expected); given(target.invoke(parameters)).willReturn(expected);
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
Object response = invoker.invoke(parameters); Object response = invoker.invoke(parameters);
...@@ -64,6 +76,20 @@ public class CachingOperationInvokerTests { ...@@ -64,6 +76,20 @@ public class CachingOperationInvokerTests {
verifyNoMoreInteractions(target); verifyNoMoreInteractions(target);
} }
@Test
public void targetAlwaysInvokedWithArguments() {
OperationInvoker target = mock(OperationInvoker.class);
Map<String, Object> parameters = new HashMap<>();
parameters.put("test", "value");
parameters.put("something", null);
given(target.invoke(parameters)).willReturn(new Object());
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
invoker.invoke(parameters);
invoker.invoke(parameters);
invoker.invoke(parameters);
verify(target, times(3)).invoke(parameters);
}
@Test @Test
public void targetInvokedWhenCacheExpires() throws InterruptedException { public void targetInvokedWhenCacheExpires() throws InterruptedException {
OperationInvoker target = mock(OperationInvoker.class); OperationInvoker target = mock(OperationInvoker.class);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment