From f49758aa004df326bfd5fa77319b364d10054236 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 14 Sep 2022 16:17:30 +0200 Subject: [PATCH] #1831 - Reset dummy invocation cache on request completion. --- .../config/WebMvcHateoasConfiguration.java | 36 +++++++++++++++++++ .../server/core/DummyInvocationUtils.java | 4 +++ .../HypermediaWebMvcConfigurerTest.java | 23 ++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java index a2dd705b..cb7fb62f 100644 --- a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java @@ -19,6 +19,9 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -26,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.hateoas.server.RepresentationModelProcessor; +import org.springframework.hateoas.server.core.DummyInvocationUtils; import org.springframework.hateoas.server.mvc.RepresentationModelProcessorHandlerMethodReturnValueHandler; import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker; import org.springframework.hateoas.server.mvc.UriComponentsContributor; @@ -33,6 +37,8 @@ import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.NonNull; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -93,6 +99,15 @@ class WebMvcHateoasConfiguration { public void extendMessageConverters(List> converters) { hypermediaConverters.augmentServer(converters); } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors(org.springframework.web.servlet.config.annotation.InterceptorRegistry) + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(DummyInvocationUtilsCacheClearer.INSTANCE); + } } /** @@ -132,4 +147,25 @@ class WebMvcHateoasConfiguration { return bean; } } + + /** + * {@link HandlerInterceptor} to clear the cache in {@link DummyInvocationUtils}. + * + * @author Oliver Drotbohm + */ + enum DummyInvocationUtilsCacheClearer implements HandlerInterceptor { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + + DummyInvocationUtils.resetCache(); + } + } } diff --git a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java index 559cd6a9..0cdcd3b3 100644 --- a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java @@ -180,6 +180,10 @@ public class DummyInvocationUtils { return new DefaultMethodInvocation(type, method, parameters); } + public static void resetCache() { + CACHE.remove(); + } + @SuppressWarnings("unchecked") private static T getProxyWithInterceptor(Class type, InvocationRecordingMethodInterceptor interceptor, ClassLoader classLoader) { diff --git a/src/test/java/org/springframework/hateoas/config/HypermediaWebMvcConfigurerTest.java b/src/test/java/org/springframework/hateoas/config/HypermediaWebMvcConfigurerTest.java index 3085d296..056e09cd 100644 --- a/src/test/java/org/springframework/hateoas/config/HypermediaWebMvcConfigurerTest.java +++ b/src/test/java/org/springframework/hateoas/config/HypermediaWebMvcConfigurerTest.java @@ -42,6 +42,7 @@ import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule; import org.springframework.hateoas.mediatype.uber.Jackson2UberModule; import org.springframework.hateoas.server.SimpleRepresentationModelAssembler; +import org.springframework.hateoas.server.core.DummyInvocationUtils; import org.springframework.hateoas.support.Employee; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -51,7 +52,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockServletContext; import org.springframework.stereotype.Controller; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.ConcurrentLruCache; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -263,6 +266,20 @@ class HypermediaWebMvcConfigurerTest { .andExpect(status().isIAmATeapot()); } + @Test // #1831 + void wipesDummyInvocationsCachePerRequest() throws Exception { + + setUp(HalWebMvcConfig.class); + + this.mockMvc.perform(get("/dynamicLink")) + .andExpect(status().isOk()); + + ThreadLocal> cache = (ThreadLocal>) ReflectionTestUtils.getField( + null, DummyInvocationUtils.class, "CACHE"); + + assertThat(cache.get().size()).isZero(); + } + private void verifyRootUriServesHypermedia(MediaType mediaType) throws Exception { verifyRootUriServesHypermedia(mediaType, mediaType); } @@ -468,6 +485,12 @@ class HypermediaWebMvcConfigurerTest { return this.assembler.toModel(newEmployee); } + + @GetMapping("/dynamicLink") + EntityModel dynamicLink() { + return EntityModel.of(employees.get(0)) // + .add(linkTo(methodOn(TestController.class).employee("1")).withSelfRel()); + } } static class EmployeeResourceAssembler implements SimpleRepresentationModelAssembler {