1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.web.servlet.support;
  17. import java.util.HashSet;
  18. import java.util.Set;
  19. import javax.servlet.ServletException;
  20. import javax.servlet.http.HttpServletRequest;
  21. import javax.servlet.http.HttpServletResponse;
  22. import org.springframework.web.context.support.WebApplicationObjectSupport;
  23. /**
  24. * Convenient superclass for any kind of web content generator,
  25. * like AbstractController and WebContentInterceptor. Can also be
  26. * used for custom handlers that have their own HandlerAdapter.
  27. *
  28. * <p>Supports HTTP cache control options. The usage of corresponding
  29. * HTTP headers can be determined via the "useExpiresHeader" and
  30. * "userCacheControlHeader" properties.
  31. *
  32. * @author Rod Johnson
  33. * @author Juergen Hoeller
  34. * @see #setUseExpiresHeader
  35. * @see #setUseCacheControlHeader
  36. * @see org.springframework.web.servlet.mvc.AbstractController
  37. * @see org.springframework.web.servlet.mvc.WebContentInterceptor
  38. */
  39. public abstract class WebContentGenerator extends WebApplicationObjectSupport {
  40. public static final String METHOD_GET = "GET";
  41. public static final String METHOD_POST = "POST";
  42. public static final String HEADER_PRAGMA = "Pragma";
  43. public static final String HEADER_EXPIRES = "Expires";
  44. public static final String HEADER_CACHE_CONTROL = "Cache-Control";
  45. /** Set of supported methods. GET and POST by default. */
  46. private Set supportedMethods;
  47. private boolean requireSession = false;
  48. /** Use HTTP 1.0 expires header? */
  49. private boolean useExpiresHeader = true;
  50. /** Use HTTP 1.1 cache-control header? */
  51. private boolean useCacheControlHeader = true;
  52. private int cacheSeconds = -1;
  53. /**
  54. * Create a new WebContentGenerator supporting GET and POST methods.
  55. */
  56. public WebContentGenerator() {
  57. this.supportedMethods = new HashSet();
  58. this.supportedMethods.add(METHOD_GET);
  59. this.supportedMethods.add(METHOD_POST);
  60. }
  61. /**
  62. * Set the HTTP methods that this content generator should support.
  63. * Default is GET and POST.
  64. */
  65. public final void setSupportedMethods(String[] supportedMethodsArray) {
  66. if (supportedMethodsArray == null || supportedMethodsArray.length == 0) {
  67. throw new IllegalArgumentException("supportedMethods must not be empty");
  68. }
  69. this.supportedMethods.clear();
  70. for (int i = 0; i < supportedMethodsArray.length; i++) {
  71. this.supportedMethods.add(supportedMethodsArray[i]);
  72. }
  73. }
  74. /**
  75. * Set if a session should be required to handle requests.
  76. */
  77. public final void setRequireSession(boolean requireSession) {
  78. this.requireSession = requireSession;
  79. }
  80. /**
  81. * Set whether to use the HTTP 1.0 expires header. Default is true.
  82. * <p>Note: Cache headers will only get applied if caching is enabled
  83. * for the current request.
  84. */
  85. public final void setUseExpiresHeader(boolean useExpiresHeader) {
  86. this.useExpiresHeader = useExpiresHeader;
  87. }
  88. /**
  89. * Set whether to use the HTTP 1.1 cache-control header. Default is true.
  90. * <p>Note: Cache headers will only get applied if caching is enabled
  91. * for the current request.
  92. */
  93. public final void setUseCacheControlHeader(boolean useCacheControlHeader) {
  94. this.useCacheControlHeader = useCacheControlHeader;
  95. }
  96. /**
  97. * Cache content for the given number of seconds. Default is -1,
  98. * indicating no generation of cache-related headers.
  99. * <p>Only if this is set to 0 (no cache) or a positive value (cache for
  100. * this many seconds) will this class generate cache headers.
  101. * <p>The headers can be overwritten by subclasses, before content is generated.
  102. */
  103. public final void setCacheSeconds(int seconds) {
  104. this.cacheSeconds = seconds;
  105. }
  106. /**
  107. * Check and prepare the given request and response according to the settings
  108. * of this generator. Checks for supported methods and a required session,
  109. * and applies the number of cache seconds specified for this generator.
  110. * @param request current HTTP request
  111. * @param response current HTTP response
  112. * @param lastModified if the mapped handler provides Last-Modified support
  113. * @throws ServletException if the request cannot be handled because a check failed
  114. */
  115. protected final void checkAndPrepare(HttpServletRequest request, HttpServletResponse response,
  116. boolean lastModified)
  117. throws ServletException {
  118. checkAndPrepare(request, response, this.cacheSeconds, lastModified);
  119. }
  120. /**
  121. * Check and prepare the given request and response according to the settings
  122. * of this generator. Checks for supported methods and a required session,
  123. * and applies the given number of cache seconds.
  124. * @param request current HTTP request
  125. * @param response current HTTP response
  126. * @param cacheSeconds positive number of seconds into the future that the
  127. * response should be cacheable for, 0 to prevent caching
  128. * @param lastModified if the mapped handler provides Last-Modified support
  129. * @throws ServletException if the request cannot be handled because a check failed
  130. */
  131. protected final void checkAndPrepare(HttpServletRequest request, HttpServletResponse response,
  132. int cacheSeconds, boolean lastModified)
  133. throws ServletException {
  134. // check whether we should support the request method
  135. String method = request.getMethod();
  136. if (!this.supportedMethods.contains(method)) {
  137. logger.info("Disallowed '" + method + "' request");
  138. throw new ServletException("This resource does not support request method '" + method + "'");
  139. }
  140. // check whether session is required
  141. if (this.requireSession) {
  142. if (request.getSession(false) == null) {
  143. throw new ServletException("This resource requires a pre-existing HttpSession: none was found");
  144. }
  145. }
  146. // do declarative cache control
  147. // revalidate if the controller supports last-modified
  148. applyCacheSeconds(response, cacheSeconds, lastModified);
  149. }
  150. /**
  151. * Prevent the response from being cached.
  152. * See www.mnot.net.cache docs.
  153. */
  154. protected final void preventCaching(HttpServletResponse response) {
  155. response.setHeader(HEADER_PRAGMA, "No-cache");
  156. if (this.useExpiresHeader) {
  157. // HTTP 1.0 header
  158. response.setDateHeader(HEADER_EXPIRES, 1L);
  159. }
  160. if (this.useCacheControlHeader) {
  161. // HTTP 1.1 header
  162. response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
  163. }
  164. }
  165. /**
  166. * Set HTTP headers to allow caching for the given number of seconds.
  167. * Does not tell the browser to revalidate the resource.
  168. * @param response current HTTP response
  169. * @param seconds number of seconds into the future that the response
  170. * should be cacheable for
  171. * @see #cacheForSeconds(javax.servlet.http.HttpServletResponse, int, boolean)
  172. */
  173. protected final void cacheForSeconds(HttpServletResponse response, int seconds) {
  174. cacheForSeconds(response, seconds, false);
  175. }
  176. /**
  177. * Set HTTP headers to allow caching for the given number of seconds.
  178. * Tells the browser to revalidate the resource if mustRevalidate is true.
  179. * @param response current HTTP response
  180. * @param seconds number of seconds into the future that the response
  181. * should be cacheable for
  182. * @param mustRevalidate whether the client should revalidate the resource
  183. * (typically only necessary for controllers with last-modified support)
  184. */
  185. protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
  186. if (this.useExpiresHeader) {
  187. // HTTP 1.0 header
  188. response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
  189. }
  190. if (this.useCacheControlHeader) {
  191. // HTTP 1.1 header
  192. String headerValue = "max-age=" + seconds;
  193. if (mustRevalidate) {
  194. headerValue += ", must-revalidate";
  195. }
  196. response.setHeader(HEADER_CACHE_CONTROL, headerValue);
  197. }
  198. }
  199. /**
  200. * Apply the given cache seconds and generate corresponding HTTP headers,
  201. * i.e. allow caching for the given number of seconds in case of a positive
  202. * value, prevent caching if given a 0 value, do nothing else.
  203. * Does not tell the browser to revalidate the resource.
  204. * @param response current HTTP response
  205. * @param seconds positive number of seconds into the future that the
  206. * response should be cacheable for, 0 to prevent caching
  207. * @see #cacheForSeconds(javax.servlet.http.HttpServletResponse, int, boolean)
  208. */
  209. protected final void applyCacheSeconds(HttpServletResponse response, int seconds) {
  210. applyCacheSeconds(response, seconds, false);
  211. }
  212. /**
  213. * Apply the given cache seconds and generate respective HTTP headers,
  214. * i.e. allow caching for the given number of seconds in case of a positive
  215. * value, prevent caching if given a 0 value, do nothing else.
  216. * @param response current HTTP response
  217. * @param seconds positive number of seconds into the future that the
  218. * response should be cacheable for, 0 to prevent caching
  219. * @param mustRevalidate whether the client should revalidate the resource
  220. * (typically only necessary for controllers with last-modified support)
  221. */
  222. protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
  223. if (seconds > 0) {
  224. cacheForSeconds(response, seconds, mustRevalidate);
  225. }
  226. else if (seconds == 0) {
  227. preventCaching(response);
  228. }
  229. // leave caching to the client otherwise
  230. }
  231. }