From e019796d978905d546d190f6a09d3af6f23d4df1 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer <paul-christian.volkmer@mni.thm.de> Date: Sun, 25 May 2014 22:28:08 +0200 Subject: [PATCH] Added CacheControl annotation and interceptor handler This should enable annotation based cache-control header values. Added a cache max age of 60 seconds to statistics resource. Proxy-Servers should cache a statistics resource for about 60 seconds. --- .../controller/StatisticsController.java | 2 + .../java/de/thm/arsnova/web/CacheControl.java | 36 +++++++++ .../web/CacheControlInterceptorHandler.java | 75 +++++++++++++++++++ .../webapp/WEB-INF/spring/arsnova-servlet.xml | 4 + .../controller/StatisticsControllerTest.java | 8 ++ 5 files changed, 125 insertions(+) create mode 100644 src/main/java/de/thm/arsnova/web/CacheControl.java create mode 100644 src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java diff --git a/src/main/java/de/thm/arsnova/controller/StatisticsController.java b/src/main/java/de/thm/arsnova/controller/StatisticsController.java index df5db1bb..3da74456 100644 --- a/src/main/java/de/thm/arsnova/controller/StatisticsController.java +++ b/src/main/java/de/thm/arsnova/controller/StatisticsController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import de.thm.arsnova.entities.Statistics; import de.thm.arsnova.services.IStatisticsService; +import de.thm.arsnova.web.CacheControl; @RestController public class StatisticsController extends AbstractController { @@ -19,6 +20,7 @@ public class StatisticsController extends AbstractController { private IStatisticsService statisticsService; @RequestMapping(method = RequestMethod.GET, value = "/statistics") + @CacheControl(maxAge = 60, policy = CacheControl.Policy.PUBLIC) public final Statistics getStatistics() { return statisticsService.getStatistics(); } diff --git a/src/main/java/de/thm/arsnova/web/CacheControl.java b/src/main/java/de/thm/arsnova/web/CacheControl.java new file mode 100644 index 00000000..336c96c2 --- /dev/null +++ b/src/main/java/de/thm/arsnova/web/CacheControl.java @@ -0,0 +1,36 @@ +package de.thm.arsnova.web; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheControl { + enum Policy { + NO_CACHE("no-cache"), + NO_STORE("no-store"), + PRIVATE("private"), + PUBLIC("public"); + + private Policy() { + this.policyString = null; + } + + private Policy(String policyString) { + this.policyString = policyString; + } + + public String getPolicyString() { + return this.policyString; + } + + private final String policyString; + } + + boolean noCache() default false; + + int maxAge() default 0; + Policy[] policy() default {}; +} diff --git a/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java b/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java new file mode 100644 index 00000000..e72fd61c --- /dev/null +++ b/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java @@ -0,0 +1,75 @@ +package de.thm.arsnova.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +@Component +public class CacheControlInterceptorHandler extends HandlerInterceptorAdapter { + + @Override + public boolean preHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler) throws Exception { + + setCacheControlResponseHeader(request, response, handler); + return super.preHandle(request, response, handler); + } + + private void setCacheControlResponseHeader( + HttpServletRequest request, + HttpServletResponse response, + Object handler) { + + CacheControl cacheControl = getCacheControlAnnotation(request, response, handler); + + if (cacheControl == null) { + return; + } + + StringBuilder headerValue = new StringBuilder(); + + if(cacheControl.policy().length > 0) { + for (CacheControl.Policy policy : cacheControl.policy()) { + if (headerValue.length() > 0) { + headerValue.append(", "); + } + headerValue.append(policy.getPolicyString()); + } + } + + if (cacheControl.noCache()) { + if (headerValue.length() > 0) { + headerValue.append(", "); + } + headerValue.append("max-age=0, no-cache"); + response.setHeader("cache-control", headerValue.toString()); + } + + if(cacheControl.maxAge() >= 0) { + if (headerValue.length() > 0) { + headerValue.append(", "); + } + headerValue.append("max-age=").append(cacheControl.maxAge()); + } + + response.setHeader("cache-control", headerValue.toString()); + } + + private CacheControl getCacheControlAnnotation( + HttpServletRequest request, + HttpServletResponse response, + Object handler + ) { + if (handler == null || !(handler instanceof HandlerMethod)) { + return null; + } + + final HandlerMethod handlerMethod = (HandlerMethod) handler; + return handlerMethod.getMethodAnnotation(CacheControl.class); + } +} diff --git a/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml b/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml index d53da559..38376efa 100644 --- a/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml +++ b/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml @@ -13,6 +13,10 @@ <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" /> + <mvc:interceptors> + <bean class="de.thm.arsnova.web.CacheControlInterceptorHandler" /> + </mvc:interceptors> + <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="favorPathExtension" value="false" /> diff --git a/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java b/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java index 79f86fcc..52419175 100644 --- a/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java +++ b/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java @@ -2,6 +2,7 @@ package de.thm.arsnova.controller; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Before; @@ -59,4 +60,11 @@ public class StatisticsControllerTest { .andExpect(status().isOk()) .andExpect(content().string("{\"answers\":0,\"questions\":0,\"openSessions\":3,\"closedSessions\":0,\"activeUsers\":0}")); } + + @Test + public final void testShouldGetCacheControlHeaderForStatistics() throws Exception { + mockMvc.perform(get("/statistics")) + .andExpect(status().isOk()) + .andExpect(header().string("cache-control", "public, max-age=60")); + } } -- GitLab