diff --git a/src/main/java/de/thm/arsnova/controller/StatisticsController.java b/src/main/java/de/thm/arsnova/controller/StatisticsController.java index df5db1bb46e64def8efe9b961c2f0c0e3c774023..3da7445638713721075c4a48ee5c3b9907ac9810 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 0000000000000000000000000000000000000000..336c96c241de63afcca7d9c210dd378eaf55b9ef --- /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 0000000000000000000000000000000000000000..e72fd61c8e22a5c36a1a5be1678bea23ad937dcd --- /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 d53da559f2020b02ebbaeab5fcf0cf56f4fca5ef..38376efa62b44004079815c276a2f8f1e50c6593 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 79f86fcc30aa14d938c60448e3c602b6c6a4c5fd..5241917564938e95014d7248ee0fbf7d90e02987 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")); + } }