package cz.cvut.fel.adaptiverestfulapi.caching; import cz.cvut.fel.adaptiverestfulapi.core.HttpContext; import cz.cvut.fel.adaptiverestfulapi.core.HttpHeaders; import cz.cvut.fel.adaptiverestfulapi.core.HttpMethod; import cz.cvut.fel.adaptiverestfulapi.core.HttpStatus; import cz.cvut.fel.adaptiverestfulapi.meta.configuration.Configuration; import cz.cvut.fel.adaptiverestfulapi.meta.model.Model; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * If-Modified-Since cache. * * Works on Entity/Identifier requests. * * It stores the timestamp on the first GET request. On another looks up for that timestamp * and compares it with HTTP header. * * On POST, PUT and DELETE requests update the timestamp or remove it. */ public class IfModifiedSinceCache extends Cache { private SimpleDateFormat dateFormat; private Map<String, Date> timestamps; public IfModifiedSinceCache() { this.dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); this.timestamps = new HashMap<>(); } @Override protected boolean load(HttpContext httpContext, Model model, Configuration configuration) { if (!HttpMethod.GET.equals(httpContext.getMethod())) { return false; } // Hit only resources with identifiers if (httpContext.getRouter().getIdentifier() == null || httpContext.getRouter().getIdentifier().isEmpty()) { return false; } // Get If-Modified-Since header Date ifModifiedSince = this.date(httpContext); if (ifModifiedSince == null) { return false; } String key = this.key(httpContext); if (!this.timestamps.containsKey(key)) { return false; } // Compare dates Date lastModified = this.timestamps.get(key); if (ifModifiedSince.after(lastModified)) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(HttpHeaders.LastModified, this.dateFormat.format(lastModified)); httpContext.response(HttpStatus.S_304, httpHeaders, null); return true; } return false; } @Override protected void save(HttpContext httpContext, Model model, Configuration configuration) { if (httpContext.getRouter().getIdentifier() == null || httpContext.getRouter().getIdentifier().isEmpty()) { return; } if (HttpMethod.GET.equals(httpContext.getMethod())) { if (!this.timestamps.containsKey(this.key(httpContext))) { this.timestamps.put(this.key(httpContext), new Date()); } Date lastModified = this.timestamps.get(this.key(httpContext)); httpContext.getResponseHeaders().add(HttpHeaders.LastModified, this.dateFormat.format(lastModified)); } else if (HttpMethod.DELETE.equals(httpContext.getMethod())) { if (this.timestamps.containsKey(this.key(httpContext))) { this.timestamps.remove(this.key(httpContext)); } } else if (HttpMethod.POST.equals(httpContext.getMethod()) || HttpMethod.PUT.equals(httpContext.getMethod())) { Date lastModified = new Date(); this.timestamps.put(this.key(httpContext), lastModified); httpContext.getResponseHeaders().add(HttpHeaders.LastModified, this.dateFormat.format(lastModified)); } } private String key(HttpContext httpContext) { return httpContext.getRouter().getResource() + "/" + httpContext.getRouter().getIdentifier(); } private Date date(HttpContext httpContext) { String value = httpContext.getRequestHeaders().get(HttpHeaders.IfModifiedSince); if (value == null || value.isEmpty()) { return null; } try { return this.dateFormat.parse(value); } catch (ParseException e) { return null; } } }