Experiment with new CachedResource framework

This commit is contained in:
Reinhard Pointner 2016-03-07 11:32:00 +00:00
parent 4e41d0dfd1
commit a46a3e48a8
7 changed files with 50 additions and 89 deletions

View File

@ -20,10 +20,24 @@ import org.w3c.dom.Document;
public class Cache { public class Cache {
public static final Duration ONE_DAY = Duration.ofDays(1);
public static Cache getCache(String name, CacheType type) { public static Cache getCache(String name, CacheType type) {
return CacheManager.getInstance().getCache(name.toLowerCase(), type); return CacheManager.getInstance().getCache(name.toLowerCase(), type);
} }
public Resource<String> text(String url, Duration expirationTime, FloodLimit limit) {
return new CachedResource2<String, String>(url, URL::new, withPermit(fetchIfModified(), r -> limit.acquirePermit() != null), getText(UTF_8), String.class::cast, expirationTime, this);
}
public Resource<Document> xml(String key, Source<String> source, Duration expirationTime) {
return new CachedResource2<String, Document>(key, source, fetchIfModified(), validateXml(getText(UTF_8)), getXml(String.class::cast), expirationTime, this);
}
public Resource<Object> json(String key, Source<String> source, Duration expirationTime) {
return new CachedResource2<String, Object>(key, source, fetchIfModified(), validateJson(getText(UTF_8)), getJson(String.class::cast), expirationTime, this);
}
private final net.sf.ehcache.Cache cache; private final net.sf.ehcache.Cache cache;
public Cache(net.sf.ehcache.Cache cache) { public Cache(net.sf.ehcache.Cache cache) {
@ -136,12 +150,4 @@ public class Cache {
} }
} }
public Resource<Document> xml(String key, Source<String> source, Duration expirationTime) {
return new CachedResource2<String, Document>(key, source, fetchIfModified(), validateXml(getText(UTF_8)), getXml(String.class::cast), DEFAULT_RETRY_LIMIT, DEFAULT_RETRY_DELAY, expirationTime, this);
}
public Resource<String> resource(String url, Duration expirationTime, FloodLimit limit) {
return new CachedResource2<String, String>(url, URL::new, withPermit(fetchIfModified(), r -> limit.acquirePermit() != null), getText(UTF_8), String.class::cast, DEFAULT_RETRY_LIMIT, DEFAULT_RETRY_DELAY, expirationTime, this);
}
} }

View File

@ -11,6 +11,7 @@ import java.time.Duration;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import net.filebot.Cache; import net.filebot.Cache;
import net.filebot.util.JsonUtilities;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@ -33,6 +34,10 @@ public class CachedResource2<K, R> implements Resource<R> {
protected final Cache cache; protected final Cache cache;
public CachedResource2(K key, Source<K> source, Fetch fetch, Transform<ByteBuffer, ? extends Object> parse, Transform<? super Object, R> cast, Duration expirationTime, Cache cache) {
this(key, source, fetch, parse, cast, DEFAULT_RETRY_LIMIT, DEFAULT_RETRY_DELAY, expirationTime, cache);
}
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) { 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) {
this.key = key; this.key = key;
this.source = source; this.source = source;
@ -124,12 +129,26 @@ public class CachedResource2<K, R> implements Resource<R> {
}; };
} }
public static <T> Transform<T, String> validateJson(Transform<T, String> parse) {
return (object) -> {
String json = parse.transform(object);
JsonUtilities.readJson(json);
return json;
};
}
public static <T> Transform<T, Document> getXml(Transform<T, String> parse) { public static <T> Transform<T, Document> getXml(Transform<T, String> parse) {
return (object) -> { return (object) -> {
return WebRequest.getDocument(parse.transform(object)); return WebRequest.getDocument(parse.transform(object));
}; };
} }
public static <T> Transform<T, Object> getJson(Transform<T, String> parse) {
return (object) -> {
return JsonUtilities.readJson(parse.transform(object));
};
}
public static Fetch fetchIfModified() { public static Fetch fetchIfModified() {
return (url, lastModified) -> { return (url, lastModified) -> {
try { try {

View File

@ -1,67 +0,0 @@
package net.filebot.web;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import javax.xml.parsers.SAXParserFactory;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class CachedXmlResource extends AbstractCachedResource<String, String> {
public CachedXmlResource(String resource) {
super(resource, String.class, ONE_DAY, 2, 1000);
}
@Override
protected Cache getCache() {
return CacheManager.getInstance().getCache("web-datasource-lv3");
}
public Document getDocument() throws IOException {
try {
return WebRequest.getDocument(get());
} catch (Exception e) {
throw new IOException(String.format("Error while loading XML resource: %s (%s)", getResourceLocation(resource), e.getMessage()));
}
}
@Override
public String process(String data) throws Exception {
// make sure xml data is valid and well-formed before caching it
SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setValidating(false);
sax.setNamespaceAware(false);
XMLReader reader = sax.newSAXParser().getXMLReader();
reader.setErrorHandler(new DefaultHandler()); // unwind on error
try {
reader.parse(new InputSource(new StringReader(data)));
} catch (SAXException e) {
throw new IOException(String.format("Malformed XML: %s (%s)", getResourceLocation(resource), e.getMessage()));
}
return data;
}
@Override
protected String fetchData(URL url, long lastModified) throws IOException {
ByteBuffer data = WebRequest.fetchIfModified(url, lastModified);
if (data == null)
return null; // not modified
return StandardCharsets.UTF_8.decode(data).toString();
}
}

View File

@ -137,7 +137,7 @@ public class OMDbClient implements MovieIdentificationService {
String url = "http://www.omdbapi.com/?" + encodeParameters(parameters, true); String url = "http://www.omdbapi.com/?" + encodeParameters(parameters, true);
Cache cache = Cache.getCache(getName(), CacheType.Weekly); Cache cache = Cache.getCache(getName(), CacheType.Weekly);
String json = cache.resource(url, Duration.ofDays(7), REQUEST_LIMIT).get(); String json = cache.text(url, Duration.ofDays(7), REQUEST_LIMIT).get();
return asMap(readJson(json)); return asMap(readJson(json));
} }

View File

@ -5,8 +5,8 @@ import static java.util.stream.Collectors.*;
import static net.filebot.util.JsonUtilities.*; import static net.filebot.util.JsonUtilities.*;
import static net.filebot.web.WebRequest.*; import static net.filebot.web.WebRequest.*;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -55,11 +55,11 @@ public class TVMazeClient extends AbstractEpisodeListProvider {
} }
@Override @Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws IOException { public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
// e.g. http://api.tvmaze.com/search/shows?q=girls // e.g. http://api.tvmaze.com/search/shows?q=girls
Object response = request("search/shows?q=" + encode(query, true)); Object response = request("search/shows?q=" + encode(query, true));
// TODO: FUTURE WORK: consider adding TVmaze aka titles for each result, e.g. http://api.tvmaze.com/shows/1/akas // FUTURE WORK: consider adding TVmaze aka titles for each result, e.g. http://api.tvmaze.com/shows/1/akas
return streamJsonObjects(response).map(it -> { return streamJsonObjects(response).map(it -> {
Object show = it.get("show"); Object show = it.get("show");
Integer id = getInteger(show, "id"); Integer id = getInteger(show, "id");
@ -69,7 +69,7 @@ public class TVMazeClient extends AbstractEpisodeListProvider {
}).collect(toList()); }).collect(toList());
} }
protected SeriesInfo fetchSeriesInfo(TVMazeSearchResult show, SortOrder sortOrder, Locale locale) throws IOException { protected SeriesInfo fetchSeriesInfo(TVMazeSearchResult show, SortOrder sortOrder, Locale locale) throws Exception {
// e.g. http://api.tvmaze.com/shows/1 // e.g. http://api.tvmaze.com/shows/1
Object response = request("shows/" + show.getId()); Object response = request("shows/" + show.getId());
@ -110,8 +110,14 @@ public class TVMazeClient extends AbstractEpisodeListProvider {
return new SeriesData(seriesInfo, episodes); return new SeriesData(seriesInfo, episodes);
} }
public Object request(String resource) throws IOException { protected Object request(String resource) throws Exception {
return new CachedJsonResource("http://api.tvmaze.com/" + resource).getJsonObject(); Cache cache = Cache.getCache(getName(), CacheType.Monthly);
Resource<Object> json = cache.json(resource, s -> getResource(resource), Cache.ONE_DAY);
return json.get();
}
protected URL getResource(String resource) throws Exception {
return new URL("http://api.tvmaze.com/" + resource);
} }
@Override @Override

View File

@ -38,8 +38,6 @@ import org.w3c.dom.Node;
public class TheTVDBClient extends AbstractEpisodeListProvider { public class TheTVDBClient extends AbstractEpisodeListProvider {
private final String host = "www.thetvdb.com";
private final Map<MirrorType, String> mirrors = MirrorType.newMap(); private final Map<MirrorType, String> mirrors = MirrorType.newMap();
private final String apikey; private final String apikey;
@ -301,10 +299,9 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
} }
protected Document getXmlResource(MirrorType mirror, String path) throws Exception { protected Document getXmlResource(MirrorType mirror, String resource) throws Exception {
Cache cache = Cache.getCache(getName(), CacheType.Monthly); Cache cache = Cache.getCache(getName(), CacheType.Monthly);
Duration expirationTime = Duration.ofDays(1); Resource<Document> xml = cache.xml(resource, s -> getResource(mirror, s), Cache.ONE_DAY);
Resource<Document> xml = cache.xml(path, s -> getResource(mirror, s), expirationTime);
return xml.get(); return xml.get();
} }
@ -362,7 +359,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
@Override @Override
public URI getEpisodeListLink(SearchResult searchResult) { public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create("http://" + host + "/?tab=seasonall&id=" + ((TheTVDBSearchResult) searchResult).getSeriesId()); return URI.create("http://www.thetvdb.com/?tab=seasonall&id=" + ((TheTVDBSearchResult) searchResult).getSeriesId());
} }
/** /**

View File

@ -15,7 +15,7 @@ import org.junit.Test;
public class TheTVDBClientTest { public class TheTVDBClientTest {
private TheTVDBClient thetvdb = new TheTVDBClient("BA864DEE427E384A"); TheTVDBClient thetvdb = new TheTVDBClient("BA864DEE427E384A");
@Test @Test
public void search() throws Exception { public void search() throws Exception {