
net.anthavio.httl.cache.CachingSender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hatatitla Show documentation
Show all versions of hatatitla Show documentation
Compact but tweakable REST client library you have been dreaming of
The newest version!
package net.anthavio.httl.cache;
import java.io.Closeable;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.concurrent.TimeUnit;
import net.anthavio.cache.CacheBase;
import net.anthavio.cache.CacheEntry;
import net.anthavio.cache.CacheEntryLoader;
import net.anthavio.cache.CacheLoadRequest;
import net.anthavio.cache.CachingSettings;
import net.anthavio.cache.ConfiguredCacheLoader;
import net.anthavio.cache.ConfiguredCacheLoader.SimpleLoader;
import net.anthavio.cache.LoadingSettings;
import net.anthavio.httl.ExtractionOperations;
import net.anthavio.httl.HttlCacheKeyProvider;
import net.anthavio.httl.HttlException;
import net.anthavio.httl.HttlRequest;
import net.anthavio.httl.HttlResponse;
import net.anthavio.httl.HttlResponseExtractor;
import net.anthavio.httl.HttlResponseExtractor.ExtractedResponse;
import net.anthavio.httl.HttlSender;
import net.anthavio.httl.HttlStatusException;
import net.anthavio.httl.SenderOperations;
import net.anthavio.httl.cache.Builders.CachingRequestBuilder;
import net.anthavio.httl.util.Cutils;
import net.anthavio.httl.util.HttlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sender wrapper that caches Responses as they are recieved from remote server.
* Server response bodies are cached as string or byte array and extraction performed every call.
*
* TODO reintroduce CachingExtractor - Note: If you want to cache extraction product, use CachingExtractor
*
* Note: For automatic updates use CacheBase#schedule
*
* @author martin.vanek
*
*/
public class CachingSender implements SenderOperations, ExtractionOperations, Closeable {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final HttlSender sender;
private final CacheBase cache;
private final HttlCacheKeyProvider keyProvider;
public CachingSender(HttlSender sender, CacheBase cache) {
if (sender == null) {
throw new IllegalArgumentException("sender is null");
}
this.sender = sender;
if (cache == null) {
throw new IllegalArgumentException("cache is null");
}
this.cache = cache;
this.keyProvider = new HttlCacheKeyProvider(sender.getConfig().getUrl().toString());
}
/**
* @return underlying sender
*/
public HttlSender getSender() {
return sender;
}
/**
* @return underlying cache
*/
public CacheBase getCache() {
return cache;
}
public void close() {
sender.close();
cache.close();
}
/**
* FIXME - this is silly method name
* Start fluent builder
*/
public CachingRequestBuilder from(HttlRequest request) {
return new CachingRequestBuilder(this, request);
}
/**
* Custom cache key from request (if exist) takes precedence
* Otherwise key derived from request URL is used
*/
protected String getCacheKey(CachingSenderRequest request) {
String cacheKey = request.getUserKey();
if (cacheKey == null) {
cacheKey = keyProvider.provideKey(request.getSenderRequest());
}
return cacheKey;
}
public static class SimpleHttpSenderLoader implements SimpleLoader {
private HttlSender sender;
private HttlRequest request;
public SimpleHttpSenderLoader(HttlSender sender, HttlRequest request) {
if (sender == null) {
throw new IllegalArgumentException("Null sender");
}
this.sender = sender;
if (request == null) {
throw new IllegalArgumentException("Null request");
}
this.request = request;
}
@Override
public CachedResponse load() throws Exception {
HttlResponse response = sender.execute(this.request);
return new CachedResponse(this.request, response);
}
}
/**
* Turns Sender Request into Cache Request
*/
public CacheLoadRequest convert(CachingSenderRequest request) {
String cacheKey = getCacheKey(request);
CachingSettings caching = new CachingSettings(cacheKey, request.getEvictTtl(), request.getExpiryTtl(),
TimeUnit.SECONDS);
SimpleHttpSenderLoader simple = new SimpleHttpSenderLoader(sender, request.getSenderRequest());
CacheEntryLoader loader = new ConfiguredCacheLoader(simple,
request.getMissingRecipe(), request.getExpiredRecipe());
LoadingSettings loading = new LoadingSettings(loader,
request.isMissingLoadAsync(), request.isExpiredLoadAsync());
return new CacheLoadRequest(caching, loading);
}
/**
* Static caching based on specified TTL
*
* XXX Having complexity of entry loading and caching on Cache side imply
* simplifies this class implementation
* causes two cache queries for expired and missing entries
*/
public CacheEntry execute(CachingSenderRequest request) {
String cacheKey = getCacheKey(request);
CacheEntry entry = cache.get(cacheKey);
if (entry != null) {
entry.getValue().setRequest(request.getSenderRequest());
if (!entry.isStale()) {
return entry; //fresh hit
} else {
CacheLoadRequest lrequest = convert(request);
return cache.load(lrequest, entry);
}
} else { //cache miss - we have nothing
CacheLoadRequest lrequest = convert(request);
return cache.load(lrequest, null);
}
}
/**
* Static caching based on specified TTL
*/
public ExtractedResponse extract(CachingSenderRequest request, Class resultType) {
HttlResponse response = execute(request).getValue();
try {
return sender.extract(response, resultType);
} finally {
Cutils.close(response);
}
}
/**
* Static caching based on specified TTL
*/
public ExtractedResponse extract(CachingSenderRequest request, HttlResponseExtractor extractor) {
HttlResponse response = execute(request).getValue();
try {
T extracted = extractor.extract(response);
return new ExtractedResponse(response, extracted);
} catch (IOException iox) {
throw new HttlException(iox);
} finally {
Cutils.close(response);
}
}
/**
* Caching based on HTTP response headers
*
* Extracted response version. Response is extracted, closed and result is returned to caller
*/
public ExtractedResponse extract(HttlRequest request, HttlResponseExtractor extractor) {
if (extractor == null) {
throw new IllegalArgumentException("Extractor is null");
}
HttlResponse response = execute(request);
try {
if (response.getHttpStatusCode() >= 300) {
throw new HttlStatusException(response);
}
T extracted = extractor.extract(response);
return new ExtractedResponse(response, extracted);
} catch (IOException iox) {
throw new HttlException(iox);
} finally {
Cutils.close(response);
}
}
/**
* Caching based on HTTP response headers
*
* Extracted response version. Response is extracted, closed and result is returned to caller
*/
public ExtractedResponse extract(HttlRequest request, Class resultType) {
if (resultType == null) {
throw new IllegalArgumentException("resultType is null");
}
HttlResponse response = execute(request);
try {
return sender.extract(response, resultType);
} finally {
Cutils.close(response);
}
}
/**
* Caching based on HTTP headers values (ETag, Last-Modified, Cache-Control, Expires).
*
* Caller is responsible for closing Response.
*/
public HttlResponse execute(HttlRequest request) {
if (request == null) {
throw new IllegalArgumentException("request is null");
}
String cacheKey = keyProvider.provideKey(request);
CacheEntry entry = cache.get(cacheKey);
if (entry != null) {
entry.getValue().setRequest(request);
if (!entry.isStale()) {
return entry.getValue(); //cache hit and not soft expired - hurray
} else {
//soft expired - verify freshness
String etag = entry.getValue().getFirstHeader("ETag");
if (etag != null) { //ETag
request.getHeaders().set("If-None-Match", etag); //XXX this modifies request so hashCode will change as well
}
String lastModified = entry.getValue().getFirstHeader("Last-Modified");
if (lastModified != null) { //Last-Modified
request.getHeaders().set("If-Modified-Since", lastModified);
}
}
} else if (request.getFirstHeader("If-None-Match") != null) {
throw new IllegalStateException("Cannot use request ETag without holding cached response");
}
HttlResponse response = sender.execute(request);
if (response.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
//only happen when we sent Etag => we have CacheEntry
//entry.setServerDate(response.getFirstHeader("Date"));
Cutils.close(response);
return entry.getValue();
}
if (response.getHttpStatusCode() < 300) {
CacheEntry entryNew = HttlUtil.buildCacheEntry(request, response);
if (entryNew != null) {
cache.set(cacheKey, entryNew);
return entryNew.getValue();
} else {
logger.info("Response http headers do not allow caching");
return response;
}
} else {
//Other then HTTP 200 OK responses are NOT cached
return response;
}
}
@Override
public String toString() {
return "CachingSender [url=" + sender.getConfig().getUrl() + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy