View Javadoc
1   package com.atlassian.plugin.servlet.util;
2   
3   import javax.servlet.http.HttpServletRequest;
4   import javax.servlet.http.HttpServletResponse;
5   import java.util.Date;
6   
7   /**
8    * This class manages the last modified date of a single HTTP resource.
9    */
10  public class LastModifiedHandler {
11      private long lastModified;
12      private String etag;
13      private static final int ONE_SECOND_MILLIS = 1000;
14  
15      public LastModifiedHandler() {
16          modified(new Date());
17      }
18  
19      public LastModifiedHandler(Date lastModifiedDate) {
20          modified(lastModifiedDate);
21      }
22  
23      /**
24       * Check whether we need to generate a response for this request. Set the necessary headers on the response, and if
25       * we don't need to provide content, set the response status to 304.
26       *
27       * If this method returns true, the caller should not perform any more processing on the request.
28       *
29       * @return true if we don't need to provide any data to satisfy this request
30       */
31      public boolean checkRequest(HttpServletRequest request, HttpServletResponse response) {
32          return checkRequest(request, response, lastModified, etag);
33      }
34  
35      /**
36       * The content has changed, reset the modified date and the etag
37       */
38      public void modified() {
39          modified(new Date());
40      }
41  
42      private void modified(Date date) {
43          lastModified = calculateLastModifiedDate(date);
44          etag = calculateEtag(lastModified);
45      }
46  
47      private static long calculateLastModifiedDate(Date lastModifiedDate) {
48          long lastModified = lastModifiedDate.getTime();
49          // resolution of 1 second
50          lastModified -= lastModified % ONE_SECOND_MILLIS;
51          return lastModified;
52      }
53  
54      private static String calculateEtag(long lastModified) {
55          return "\"" + lastModified + "\"";
56      }
57  
58      /**
59       * This static method is used when the resource being served by the servlet keeps track of the last modified date,
60       * and so no state needs to be maintained by this handler.
61       */
62      public static boolean checkRequest(HttpServletRequest request, HttpServletResponse response, Date lastModifiedDate) {
63          long lastModified = calculateLastModifiedDate(lastModifiedDate);
64          return checkRequest(request, response, lastModified, calculateEtag(lastModified));
65      }
66  
67      private static boolean checkRequest(HttpServletRequest request, HttpServletResponse response, long lastModified, String etagString) {
68          if ("true".equals(System.getProperty("atlassian.disable.caches", "false")))
69              return false;
70  
71          response.setDateHeader("Last-Modified", lastModified);
72          response.setHeader("ETag", etagString);
73  
74          long ifModifiedSince = request.getDateHeader("If-Modified-Since");
75          String ifNoneMatch = request.getHeader("If-None-Match");
76          if (noConditionalGetHeadersFound(ifModifiedSince, ifNoneMatch)
77                  || isContentModifiedSince(ifModifiedSince, lastModified)
78                  || !etagMatches(ifNoneMatch, etagString)) {
79              return false;
80          }
81          response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
82          return true;
83      }
84  
85      private static boolean etagMatches(String ifNoneMatch, String etagString) {
86          return ifNoneMatch != null && ifNoneMatch.equals(etagString);
87      }
88  
89      private static boolean isContentModifiedSince(long ifModifiedSince, long lastModified) {
90          return ifModifiedSince != -1 && ifModifiedSince < lastModified;
91      }
92  
93      private static boolean noConditionalGetHeadersFound(long ifModifiedSince, String ifNoneMatch) {
94          return ifModifiedSince == -1 && ifNoneMatch == null;
95      }
96  }