package org.jboss.resteasy.client.jaxrs.cache;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.util.DateUtil;
import org.jboss.resteasy.util.MediaTypeHelper;
import org.jboss.resteasy.util.ReadFromStream;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unchecked")
public class CacheInterceptor implements ClientRequestFilter, ClientResponseFilter
{
protected BrowserCache cache;
public CacheInterceptor(final BrowserCache cache)
{
LogMessages.LOGGER.debugf("Interceptor : %s, Method : CacheInterceptor", getClass().getName());
this.cache = cache;
}
@Override
public void filter(ClientRequestContext request) throws IOException
{
if (!request.getMethod().equalsIgnoreCase("GET")) return;
try
{
BrowserCache.Entry entry = getEntry(request);
if (entry == null) return;
if (entry.expired())
{
cache.remove(request.getUri().toString(), entry.getMediaType());
BrowserCache.Header[] headers = entry.getValidationHeaders();
for (BrowserCache.Header header : headers)
{
request.getHeaders().putSingle(header.getName(), header.getValue());
}
request.setProperty("expired.cache.entry", entry);
return;
}
request.setProperty("cached", "cached");
request.abortWith(cachedResponse(entry));
}
catch (IOException io)
{
throw io;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private Response cachedResponse(BrowserCache.Entry entry)
{
ByteArrayInputStream bais = new ByteArrayInputStream(entry.getCached());
Response.ResponseBuilder builder = Response.ok().entity(bais);
for (Map.Entry<String, List<String>> header : entry.getHeaders().entrySet())
{
for (String val : header.getValue())
{
builder.header(header.getKey(), val);
}
}
return builder.build();
}
@Override
public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException
{
if (!request.getMethod().equalsIgnoreCase("GET") || request.getProperty("cached") != null) return;
else if (response.getStatus() == 304)
{
BrowserCache.Entry entry = (BrowserCache.Entry)request.getProperty("expired.cache.entry");
updateOnNotModified(request, entry, response);
return;
}
else if (response.getStatus() == 200)
{
cache(request, response);
}
}
private void useCacheEntry(ClientResponseContext response, BrowserCache.Entry entry)
{
ByteArrayInputStream bais = new ByteArrayInputStream(entry.getCached());
response.setEntityStream(bais);
response.setStatus(200);
for (Map.Entry<String, List<String>> header : entry.getHeaders().entrySet())
{
response.getHeaders().remove(header.getKey());
for (String val : header.getValue())
{
response.getHeaders().add(header.getKey(), val);
}
}
}
private void cache(ClientRequestContext request, ClientResponseContext response) throws IOException
{
if (response.getStatus() != 200) return;
cacheIfPossible(request, response);
}
public void updateOnNotModified(ClientRequestContext request, BrowserCache.Entry old, ClientResponseContext response)
{
old.getHeaders().remove(HttpHeaders.CACHE_CONTROL);
old.getHeaders().remove(HttpHeaders.EXPIRES);
old.getHeaders().remove(HttpHeaders.LAST_MODIFIED);
String cc = (String) response.getHeaderString(HttpHeaders.CACHE_CONTROL);
String exp = (String) response.getHeaderString(HttpHeaders.EXPIRES);
int expires = -1;
if (cc != null)
{
CacheControl cacheControl = CacheControl.valueOf(cc);
if (cacheControl.isNoCache())
{
useCacheEntry(response, old);
return;
}
expires = cacheControl.getMaxAge();
}
else if (exp != null)
{
Date date = DateUtil.parseDate(exp);
expires = (int) ((date.getTime() - System.currentTimeMillis()) / 1000);
}
if (cc != null)
{
old.getHeaders().putSingle(HttpHeaders.CACHE_CONTROL, cc);
}
if (exp != null)
{
old.getHeaders().putSingle(HttpHeaders.CACHE_CONTROL, exp);
}
String lastModified = (String) response.getHeaderString(HttpHeaders.LAST_MODIFIED);
String etag = (String) response.getHeaderString(HttpHeaders.ETAG);
if (etag == null) etag = old.getHeaders().getFirst(HttpHeaders.ETAG);
else old.getHeaders().putSingle(HttpHeaders.ETAG, etag);
if (lastModified != null)
{
old.getHeaders().putSingle(HttpHeaders.LAST_MODIFIED, lastModified);
}
if (etag == null && lastModified == null && cc == null && exp == null)
{
useCacheEntry(response, old);
return;
}
BrowserCache.Entry entry = cache.put(request.getUri().toString(), old.getMediaType(), old.getHeaders(), old.getCached(), expires, etag, lastModified);
useCacheEntry(response, entry);
}
public void cacheIfPossible(ClientRequestContext request, ClientResponseContext response) throws IOException
{
String cc = (String) response.getHeaderString(HttpHeaders.CACHE_CONTROL);
String exp = (String) response.getHeaderString(HttpHeaders.EXPIRES);
int expires = -1;
if (cc != null)
{
CacheControl cacheControl = CacheControl.valueOf(cc);
if (cacheControl.isNoCache()) return;
expires = cacheControl.getMaxAge();
}
else if (exp != null)
{
Date date = DateUtil.parseDate(exp);
expires = (int) ((date.getTime() - System.currentTimeMillis()) / 1000);
}
String lastModified = (String) response.getHeaderString(HttpHeaders.LAST_MODIFIED);
String etag = (String) response.getHeaderString(HttpHeaders.ETAG);
String contentType = (String) response.getHeaderString(HttpHeaders.CONTENT_TYPE);
String accept = (String) request.getHeaderString(HttpHeaders.ACCEPT);
byte[] cached = ReadFromStream.readFromStream(1024, response.getEntityStream());
MediaType mediaType = MediaType.valueOf(contentType);
if (accept != null) {
for (String acceptItem : accept.split("\\s*,\\s*")) {
MediaType acceptMediaType = MediaType.valueOf(acceptItem);
if (mediaType.isCompatible(acceptMediaType)) {
mediaType = acceptMediaType;
break;
}
}
}
final BrowserCache.Entry entry = cache.put(request.getUri().toString(), mediaType,
response.getHeaders(), cached, expires, etag, lastModified);
response.setEntityStream(new ByteArrayInputStream(cached));
}
protected BrowserCache.Entry getEntry(ClientRequestContext request) throws Exception
{
String uri = request.getUri().toString();
List<MediaType> acceptableMediaTypes = request.getAcceptableMediaTypes();
BrowserCache.Entry entry = null;
if (acceptableMediaTypes.size() > 0)
{
for (MediaType accept : acceptableMediaTypes)
{
entry = cache.get(uri, accept);
if (entry != null) return entry;
if (MediaTypeHelper.isTextLike(accept))
{
entry = cache.get(uri, accept.withCharset("UTF-8"));
if (entry != null) return entry;
}
}
}
else
{
return cache.getAny(uri);
}
return null;
}
}