2016-03-06 17:21:13 -05:00
|
|
|
package net.filebot.web;
|
|
|
|
|
|
|
|
import static net.filebot.Logging.*;
|
|
|
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.util.concurrent.Callable;
|
|
|
|
|
|
|
|
import net.filebot.Cache;
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
import org.w3c.dom.Document;
|
|
|
|
|
|
|
|
public class CachedResource2<K, R> implements Resource<R> {
|
2016-03-06 17:21:13 -05:00
|
|
|
|
|
|
|
public static final int DEFAULT_RETRY_LIMIT = 2;
|
|
|
|
public static final Duration DEFAULT_RETRY_DELAY = Duration.ofSeconds(2);
|
|
|
|
|
|
|
|
protected final K key;
|
|
|
|
|
|
|
|
protected final Source<K> source;
|
|
|
|
protected final Fetch fetch;
|
2016-03-07 05:55:45 -05:00
|
|
|
protected final Transform<ByteBuffer, ? extends Object> parse;
|
|
|
|
protected final Transform<? super Object, R> cast;
|
2016-03-06 17:21:13 -05:00
|
|
|
|
|
|
|
protected final Duration expirationTime;
|
|
|
|
|
|
|
|
protected final int retryCountLimit;
|
|
|
|
protected final long retryWaitTime;
|
|
|
|
|
|
|
|
protected final Cache cache;
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
public CachedResource2(K key, Source<K> source, Fetch fetch, Transform<ByteBuffer, ? extends Object> parse, Transform<? super Object, R> cast, int retryCountLimit, Duration retryWaitTime, Duration expirationTime, Cache cache) {
|
2016-03-06 17:21:13 -05:00
|
|
|
this.key = key;
|
|
|
|
this.source = source;
|
|
|
|
this.fetch = fetch;
|
|
|
|
this.parse = parse;
|
2016-03-07 05:55:45 -05:00
|
|
|
this.cast = cast;
|
2016-03-06 17:21:13 -05:00
|
|
|
this.expirationTime = expirationTime;
|
|
|
|
this.retryCountLimit = retryCountLimit;
|
|
|
|
this.retryWaitTime = retryWaitTime.toMillis();
|
|
|
|
this.cache = cache;
|
|
|
|
}
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
@Override
|
2016-03-06 17:21:13 -05:00
|
|
|
public synchronized R get() throws Exception {
|
2016-03-07 05:55:45 -05:00
|
|
|
Object value = cache.computeIfStale(key, expirationTime, element -> {
|
2016-03-06 17:21:13 -05:00
|
|
|
URL resource = source.source(key);
|
|
|
|
long lastModified = element == null ? 0 : element.getLatestOfCreationAndUpdateTime();
|
|
|
|
|
|
|
|
debug.fine(format("Fetch %s (If-Modified-Since: %tc)", resource, lastModified));
|
|
|
|
|
|
|
|
try {
|
|
|
|
ByteBuffer data = retry(() -> fetch.fetch(resource, lastModified), retryCountLimit, lastModified);
|
|
|
|
|
|
|
|
// 304 Not Modified
|
|
|
|
if (data == null && element != null && element.getObjectValue() != null) {
|
|
|
|
return element.getObjectValue();
|
|
|
|
}
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
return parse.transform(data);
|
2016-03-06 17:21:13 -05:00
|
|
|
} catch (IOException e) {
|
|
|
|
debug.fine(format("Fetch failed => %s", e));
|
|
|
|
|
|
|
|
// use previously cached data if possible
|
|
|
|
if (element == null || element.getObjectValue() == null) {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
return element.getObjectKey();
|
|
|
|
}
|
|
|
|
});
|
2016-03-07 05:55:45 -05:00
|
|
|
|
|
|
|
return cast.transform(value);
|
2016-03-06 17:21:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
protected <T> T retry(Callable<T> callable, int retryCount, long retryWaitTime) throws Exception {
|
|
|
|
try {
|
|
|
|
return callable.call();
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
// resource does not exist, do not retry
|
|
|
|
throw e;
|
|
|
|
} catch (IOException e) {
|
|
|
|
// retry or rethrow exception
|
|
|
|
if (retryCount > 0) {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
Thread.sleep(retryWaitTime);
|
|
|
|
return retry(callable, retryCount - 1, retryWaitTime * 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@FunctionalInterface
|
|
|
|
public interface Source<K> {
|
|
|
|
URL source(K key) throws Exception;
|
|
|
|
}
|
|
|
|
|
|
|
|
@FunctionalInterface
|
|
|
|
public interface Fetch {
|
|
|
|
ByteBuffer fetch(URL url, long lastModified) throws Exception;
|
|
|
|
}
|
|
|
|
|
|
|
|
@FunctionalInterface
|
2016-03-07 05:55:45 -05:00
|
|
|
public interface Transform<T, R> {
|
|
|
|
R transform(T object) throws Exception;
|
|
|
|
}
|
|
|
|
|
|
|
|
@FunctionalInterface
|
|
|
|
public interface Permit<P> {
|
|
|
|
boolean acquirePermit(URL resource) throws Exception;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Transform<ByteBuffer, String> getText(Charset charset) {
|
|
|
|
return (data) -> charset.decode(data).toString();
|
2016-03-06 17:21:13 -05:00
|
|
|
}
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
public static <T> Transform<T, String> validateXml(Transform<T, String> parse) {
|
|
|
|
return (object) -> {
|
|
|
|
String xml = parse.transform(object);
|
|
|
|
WebRequest.validateXml(xml);
|
|
|
|
return xml;
|
|
|
|
};
|
2016-03-06 17:21:13 -05:00
|
|
|
}
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
public static <T> Transform<T, Document> getXml(Transform<T, String> parse) {
|
|
|
|
return (object) -> {
|
|
|
|
return WebRequest.getDocument(parse.transform(object));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Fetch fetchIfModified() {
|
2016-03-06 17:21:13 -05:00
|
|
|
return (url, lastModified) -> {
|
|
|
|
try {
|
|
|
|
return WebRequest.fetchIfModified(url, lastModified);
|
|
|
|
} catch (FileNotFoundException e) {
|
2016-03-07 05:55:45 -05:00
|
|
|
debug.warning(format("Resource not found: %s => %s", url, e));
|
2016-03-06 17:21:13 -05:00
|
|
|
return ByteBuffer.allocate(0);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-03-07 05:55:45 -05:00
|
|
|
public static Fetch withPermit(Fetch fetch, Permit<?> permit) {
|
|
|
|
return (url, lastModified) -> {
|
|
|
|
if (permit.acquirePermit(url)) {
|
|
|
|
return fetch.fetch(url, lastModified);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-03-06 17:21:13 -05:00
|
|
|
}
|