From 500a4972e1c1760b0da0bafe9a4265362a35184b Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sun, 6 Mar 2016 18:11:30 +0000 Subject: [PATCH] Rewrite caching --- .classpath | 2 +- source/net/filebot/Cache.java | 106 ++++++++------ source/net/filebot/CacheManager.java | 136 ++++++++++++++++++ source/net/filebot/CacheType.java | 32 +++++ source/net/filebot/Main.java | 96 +------------ .../net/filebot/format/MediaBindingBean.java | 7 +- .../filebot/hash/VerificationUtilities.java | 18 +-- source/net/filebot/ui/MainFrame.java | 2 +- .../filebot/ui/rename/EpisodeListMatcher.java | 8 +- source/net/filebot/web/AcoustIDClient.java | 3 +- source/net/filebot/web/AnidbClient.java | 5 +- .../net/filebot/web/OpenSubtitlesClient.java | 5 +- source/net/filebot/web/ShooterSubtitles.java | 3 +- source/net/filebot/web/TVMazeClient.java | 3 +- source/net/filebot/web/TheTVDBClient.java | 3 +- 15 files changed, 257 insertions(+), 172 deletions(-) create mode 100644 source/net/filebot/CacheManager.java create mode 100644 source/net/filebot/CacheType.java diff --git a/.classpath b/.classpath index 05b225ac..ced21f30 100644 --- a/.classpath +++ b/.classpath @@ -8,7 +8,7 @@ - + diff --git a/source/net/filebot/Cache.java b/source/net/filebot/Cache.java index 217eff40..ce68e380 100644 --- a/source/net/filebot/Cache.java +++ b/source/net/filebot/Cache.java @@ -1,75 +1,91 @@ package net.filebot; -import java.io.Serializable; -import java.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; +import static net.filebot.Logging.*; + +import java.io.Serializable; +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.function.Predicate; -import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; public class Cache { - public static final String EPHEMERAL = "ephemeral-memory"; - public static final String PERSISTENT = "persistent-memory"; - - public static Cache getCache(String name) { - return new Cache(CacheManager.getInstance().getCache(name)); + public static Cache getCache(String name, CacheType type) { + return CacheManager.getInstance().getCache(name.toLowerCase(), type); } private final net.sf.ehcache.Cache cache; - protected Cache(net.sf.ehcache.Cache cache) { + public Cache(net.sf.ehcache.Cache cache) { this.cache = cache; } + public Object get(Object key) { + try { + return cache.get(key).getObjectValue(); + } catch (Exception e) { + debug.warning(format("Bad cache state: %s => %s", key, e)); + } + return null; + } + + public Object computeIf(Object key, Predicate condition, Callable callable) throws Exception { + // get if present + try { + Element element = cache.get(key); + if (element != null && condition.test(element)) { + return element.getObjectValue(); + } + } catch (Exception e) { + debug.warning(format("Bad cache state: %s => %s", key, e)); + } + + // compute if absent + Object value = callable.call(); + try { + cache.put(new Element(key, value)); + } catch (Exception e) { + debug.warning(format("Bad cache state: %s => %s", key, e)); + } + return value; + } + + public Object computeIfAbsent(Object key, Callable callable) throws Exception { + return computeIf(key, Element::isExpired, callable); + } + + public Object computeIfStale(Object key, Duration expirationTime, Callable callable) throws Exception { + return computeIf(key, isStale(expirationTime), callable); + } + + private Predicate isStale(Duration expirationTime) { + return (element) -> element.isExpired() || System.currentTimeMillis() - element.getLatestOfCreationAndUpdateTime() < expirationTime.toMillis(); + } + public void put(Object key, Object value) { try { cache.put(new Element(key, value)); - } catch (Throwable e) { - Logger.getLogger(Cache.class.getName()).log(Level.WARNING, e.getMessage()); - remove(key); // fail-safe - } - } - - public Object get(Object key) { - return get(key, Object.class); - } - - public T get(Object key, Class type) { - try { - Element element = cache.get(key); - if (element != null && key.equals(element.getKey())) { - return type.cast(element.getValue()); - } } catch (Exception e) { - Logger.getLogger(Cache.class.getName()).log(Level.WARNING, e.getMessage()); - remove(key); // fail-safe + debug.warning(format("Bad cache state: %s => %s", key, e)); } - - return null; } public void remove(Object key) { try { cache.remove(key); - } catch (Exception e1) { - Logger.getLogger(Cache.class.getName()).log(Level.SEVERE, e1.getMessage()); - try { - Logger.getLogger(Cache.class.getName()).log(Level.INFO, "Cached data has become invalid: Clearing cache now"); - cache.removeAll(); - } catch (Exception e2) { - Logger.getLogger(Cache.class.getName()).log(Level.SEVERE, e2.getMessage()); - try { - Logger.getLogger(Cache.class.getName()).log(Level.INFO, "Cache has become invalid: Reset all caches"); - cache.getCacheManager().clearAll(); - } catch (Exception e3) { - Logger.getLogger(Cache.class.getName()).log(Level.SEVERE, e3.getMessage()); - } - } + } catch (Exception e) { + debug.warning(format("Bad cache state: %s => %s", key, e)); } } + @Deprecated + public T get(Object key, Class type) { + return type.cast(get(key)); + } + + @Deprecated public static class Key implements Serializable { protected Object[] fields; diff --git a/source/net/filebot/CacheManager.java b/source/net/filebot/CacheManager.java new file mode 100644 index 00000000..776643c8 --- /dev/null +++ b/source/net/filebot/CacheManager.java @@ -0,0 +1,136 @@ +package net.filebot; + +import static net.filebot.Logging.*; +import static net.filebot.Settings.*; +import static net.filebot.util.FileUtilities.*; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.Charset; +import java.nio.file.StandardOpenOption; +import java.util.Scanner; + +import net.sf.ehcache.CacheException; +import net.sf.ehcache.config.Configuration; +import net.sf.ehcache.config.DiskStoreConfiguration; + +public class CacheManager { + + private static final CacheManager instance = new CacheManager(); + + public static CacheManager getInstance() { + return instance; + } + + private final net.sf.ehcache.CacheManager manager; + + public CacheManager() { + try { + this.manager = net.sf.ehcache.CacheManager.create(getConfiguration()); + } catch (IOException e) { + throw new CacheException(e); + } + } + + public Cache getCache(String name, CacheType type) { + String cacheName = name + "_" + type.ordinal(); + if (!manager.cacheExists(cacheName)) { + debug.config("Create cache: " + cacheName); + manager.addCache(new net.sf.ehcache.Cache(type.getConfiguration(cacheName))); + } + return new Cache(manager.getCache(cacheName)); + } + + public void clearAll() { + manager.clearAll(); + } + + private Configuration getConfiguration() throws IOException { + Configuration config = new Configuration(); + config.addDiskStore(getDiskStoreConfiguration()); + return config; + } + + private DiskStoreConfiguration getDiskStoreConfiguration() throws IOException { + // prepare cache folder for this application instance + File cacheRoot = getApplicationCache().getCanonicalFile(); + + for (int i = 0; true; i++) { + File cache = new File(cacheRoot, Integer.toString(i)); + + // make sure cache is readable and writable + createFolders(cache); + + final File lockFile = new File(cache, ".lock"); + boolean isNewCache = !lockFile.exists(); + + final FileChannel channel = FileChannel.open(lockFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); + final FileLock lock = channel.tryLock(); + + if (lock != null) { + debug.config(format("Using persistent disk cache %s", cache)); + + int applicationRevision = getApplicationRevisionNumber(); + int cacheRevision = 0; + try { + cacheRevision = new Scanner(channel, "UTF-8").nextInt(); + } catch (Exception e) { + // ignore + } + + if (cacheRevision != applicationRevision && applicationRevision > 0 && !isNewCache) { + debug.config(format("App version (r%d) does not match cache version (r%d): reset cache", applicationRevision, cacheRevision)); + + // tag cache with new revision number + isNewCache = true; + + // delete all files related to previous cache instances + for (File it : getChildren(cache)) { + if (!it.equals(lockFile)) { + delete(cache); + } + } + } + + if (isNewCache) { + // set new cache revision + channel.position(0); + channel.write(Charset.forName("UTF-8").encode(String.valueOf(applicationRevision))); + channel.truncate(channel.position()); + } + + // make sure to orderly shutdown cache + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() { + try { + manager.shutdown(); + } catch (Exception e) { + // ignore, shutting down anyway + } + try { + lock.release(); + } catch (Exception e) { + // ignore, shutting down anyway + } + try { + channel.close(); + } catch (Exception e) { + // ignore, shutting down anyway + } + } + }); + + // cache for this application instance is successfully set up and locked + return new DiskStoreConfiguration().path(cache.getPath()); + } + + // try next lock file + channel.close(); + } + } + +} diff --git a/source/net/filebot/CacheType.java b/source/net/filebot/CacheType.java new file mode 100644 index 00000000..241ac7a8 --- /dev/null +++ b/source/net/filebot/CacheType.java @@ -0,0 +1,32 @@ +package net.filebot; + +import java.time.Duration; + +import net.sf.ehcache.config.CacheConfiguration; + +public enum CacheType { + + Persistent(Duration.ofDays(180), true), + + Monthly(Duration.ofDays(60), true), + + Weekly(Duration.ofDays(12), true), + + Daily(Duration.ofDays(1), true), + + Ephemeral(Duration.ofHours(2), false); + + final long timeToLiveSeconds; + final boolean diskPersistent; + + CacheType(Duration timeToLive, boolean diskPersistent) { + this.timeToLiveSeconds = timeToLive.getSeconds(); + this.diskPersistent = diskPersistent; + } + + CacheConfiguration getConfiguration(String name) { + // Strategy.LOCALTEMPSWAP is not restartable so we can't but use the deprecated disk persistent code (see http://stackoverflow.com/a/24623527/1514467) + return new CacheConfiguration().name(name).maxEntriesLocalHeap(diskPersistent ? 200 : 0).maxEntriesLocalDisk(0).eternal(false).timeToLiveSeconds(timeToLiveSeconds).timeToIdleSeconds(timeToLiveSeconds).overflowToDisk(diskPersistent).diskPersistent(diskPersistent); + } + +} diff --git a/source/net/filebot/Main.java b/source/net/filebot/Main.java index b0e3a0ca..421f8f6d 100644 --- a/source/net/filebot/Main.java +++ b/source/net/filebot/Main.java @@ -19,8 +19,6 @@ import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.charset.Charset; import java.nio.file.StandardOpenOption; import java.security.CodeSource; import java.security.Permission; @@ -30,7 +28,6 @@ import java.security.Policy; import java.security.ProtectionDomain; import java.util.Locale; import java.util.Properties; -import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; @@ -62,7 +59,6 @@ import net.filebot.util.PreferencesMap.PreferencesEntry; import net.filebot.util.TeePrintStream; import net.filebot.web.CachedResource; import net.miginfocom.swing.MigLayout; -import net.sf.ehcache.CacheManager; import org.kohsuke.args4j.CmdLineException; import org.w3c.dom.NodeList; @@ -100,7 +96,6 @@ public class Main { } } - initializeCache(); CacheManager.getInstance().clearAll(); } @@ -136,7 +131,7 @@ public class Main { createFolders(getApplicationTempFolder()); // initialize this stuff before anything else - initializeCache(); + CacheManager.getInstance(); initializeSecurityManager(); // update system properties @@ -443,95 +438,6 @@ public class Main { window.setBounds(x, y, width, height); } - /** - * Shutdown ehcache properly, so that disk-persistent stores can actually be saved to disk - */ - private static void initializeCache() throws Exception { - // prepare cache folder for this application instance - File cacheRoot = getApplicationCache(); - createFolders(cacheRoot); - - try { - for (int i = 0; true; i++) { - File cache = new File(cacheRoot, String.format("%d", i)); - - // make sure cache is accessible - createFolders(cache); - - final File lockFile = new File(cache, ".lock"); - boolean isNewCache = !lockFile.exists(); - - final FileChannel channel = FileChannel.open(lockFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - final FileLock lock = channel.tryLock(); - - if (lock != null) { - // setup cache dir for ehcache - System.setProperty("ehcache.disk.store.dir", cache.getAbsolutePath()); - - int applicationRevision = getApplicationRevisionNumber(); - int cacheRevision = 0; - try { - cacheRevision = new Scanner(channel, "UTF-8").nextInt(); - } catch (Exception e) { - // ignore - } - - if (cacheRevision != applicationRevision && applicationRevision > 0 && !isNewCache) { - Logger.getLogger(Main.class.getName()).log(Level.WARNING, format("App version (r%d) does not match cache version (r%d): reset cache", applicationRevision, cacheRevision)); - - // tag cache with new revision number - isNewCache = true; - - // delete all files related to previous cache instances - for (File it : getChildren(cache)) { - if (!it.equals(lockFile)) { - delete(cache); - } - } - } - - if (isNewCache) { - // set new cache revision - channel.position(0); - channel.write(Charset.forName("UTF-8").encode(String.valueOf(applicationRevision))); - channel.truncate(channel.position()); - } - - // make sure to orderly shutdown cache - Runtime.getRuntime().addShutdownHook(new Thread() { - - @Override - public void run() { - try { - CacheManager.getInstance().shutdown(); - } catch (Exception e) { - // ignore, shutting down anyway - } - try { - lock.release(); - } catch (Exception e) { - // ignore, shutting down anyway - } - try { - channel.close(); - } catch (Exception e) { - // ignore, shutting down anyway - } - } - }); - - // cache for this application instance is successfully set up and locked - return; - } - - // try next lock file - channel.close(); - } - } catch (Exception e) { - throw new Exception("Failed to initialize cache: " + e.toString(), e); - } - } - /** * Initialize default SecurityManager and grant all permissions via security policy. Initialization is required in order to run {@link ExpressionFormat} in a secure sandbox. */ diff --git a/source/net/filebot/format/MediaBindingBean.java b/source/net/filebot/format/MediaBindingBean.java index eeb3710d..8ff44a30 100644 --- a/source/net/filebot/format/MediaBindingBean.java +++ b/source/net/filebot/format/MediaBindingBean.java @@ -34,6 +34,8 @@ import java.util.TreeSet; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.Language; import net.filebot.MediaTypes; import net.filebot.MetaAttributeView; @@ -412,7 +414,7 @@ public class MediaBindingBean { } @Define("crc32") - public String getCRC32() throws IOException, InterruptedException { + public String getCRC32() throws Exception { // use inferred media file File inferredMediaFile = getInferredMediaFile(); @@ -444,7 +446,8 @@ public class MediaBindingBean { } // calculate checksum from file - return crc32(inferredMediaFile); + Cache cache = Cache.getCache("crc32", CacheType.Ephemeral); + return (String) cache.computeIfAbsent(inferredMediaFile, () -> crc32(inferredMediaFile)); } @Define("fn") diff --git a/source/net/filebot/hash/VerificationUtilities.java b/source/net/filebot/hash/VerificationUtilities.java index aacca952..e34ffd19 100644 --- a/source/net/filebot/hash/VerificationUtilities.java +++ b/source/net/filebot/hash/VerificationUtilities.java @@ -10,8 +10,6 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.filebot.Cache; - public final class VerificationUtilities { /** @@ -106,23 +104,9 @@ public final class VerificationUtilities { } public static String crc32(File file) throws IOException, InterruptedException { - // try to get checksum from cache - Cache cache = Cache.getCache(Cache.EPHEMERAL); - - String hash = cache.get(file, String.class); - if (hash != null) { - return hash; - } - - // compute and cache checksum - hash = computeHash(file, HashType.SFV); - cache.put(file, hash); - return hash; + return computeHash(file, HashType.SFV); } - /** - * Dummy constructor to prevent instantiation. - */ private VerificationUtilities() { throw new UnsupportedOperationException(); } diff --git a/source/net/filebot/ui/MainFrame.java b/source/net/filebot/ui/MainFrame.java index f6159f7f..b511f3b9 100644 --- a/source/net/filebot/ui/MainFrame.java +++ b/source/net/filebot/ui/MainFrame.java @@ -38,6 +38,7 @@ import javax.swing.border.LineBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import net.filebot.CacheManager; import net.filebot.Settings; import net.filebot.cli.GroovyPad; import net.filebot.mac.MacAppUtilities; @@ -52,7 +53,6 @@ import net.filebot.util.ui.DefaultFancyListCellRenderer; import net.filebot.util.ui.ShadowBorder; import net.filebot.util.ui.SwingUI; import net.miginfocom.swing.MigLayout; -import net.sf.ehcache.CacheManager; public class MainFrame extends JFrame { diff --git a/source/net/filebot/ui/rename/EpisodeListMatcher.java b/source/net/filebot/ui/rename/EpisodeListMatcher.java index bcf944e0..8dc54320 100644 --- a/source/net/filebot/ui/rename/EpisodeListMatcher.java +++ b/source/net/filebot/ui/rename/EpisodeListMatcher.java @@ -39,6 +39,7 @@ import javax.swing.Action; import javax.swing.SwingUtilities; import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.Settings; import net.filebot.similarity.CommonSequenceMatcher; import net.filebot.similarity.EpisodeMatcher; @@ -56,16 +57,17 @@ class EpisodeListMatcher implements AutoCompleteMatcher { private boolean useAnimeIndex; private boolean useSeriesIndex; + // remember user selections + private Cache persistentSelectionMemory; + // only allow one fetch session at a time so later requests can make use of cached results private final Object providerLock = new Object(); - // remember user selections - private final Cache persistentSelectionMemory = Cache.getCache(Cache.PERSISTENT); - public EpisodeListMatcher(EpisodeListProvider provider, boolean useSeriesIndex, boolean useAnimeIndex) { this.provider = provider; this.useSeriesIndex = useSeriesIndex; this.useAnimeIndex = useAnimeIndex; + this.persistentSelectionMemory = Cache.getCache("selection_" + provider.getName(), CacheType.Persistent); } protected SearchResult selectSearchResult(final String query, final List searchResults, Map selectionMemory, boolean autodetection, final Component parent) throws Exception { diff --git a/source/net/filebot/web/AcoustIDClient.java b/source/net/filebot/web/AcoustIDClient.java index c5ebb8fc..68a220cd 100644 --- a/source/net/filebot/web/AcoustIDClient.java +++ b/source/net/filebot/web/AcoustIDClient.java @@ -28,6 +28,7 @@ import java.util.logging.Logger; import javax.swing.Icon; import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.ResourceManager; public class AcoustIDClient implements MusicIdentificationService { @@ -51,7 +52,7 @@ public class AcoustIDClient implements MusicIdentificationService { } public Cache getCache() { - return Cache.getCache("web-datasource-lv3"); + return Cache.getCache(getName(), CacheType.Monthly); } @Override diff --git a/source/net/filebot/web/AnidbClient.java b/source/net/filebot/web/AnidbClient.java index f7f79ddf..6ac4397c 100644 --- a/source/net/filebot/web/AnidbClient.java +++ b/source/net/filebot/web/AnidbClient.java @@ -30,7 +30,8 @@ import java.util.zip.GZIPInputStream; import javax.swing.Icon; -import net.filebot.Cache; +import net.filebot.CacheManager; +import net.filebot.CacheType; import net.filebot.ResourceManager; import org.jsoup.Jsoup; @@ -78,7 +79,7 @@ public class AnidbClient extends AbstractEpisodeListProvider { @Override public ResultCache getCache() { - return new ResultCache(getName(), Cache.getCache("web-datasource-lv2")); + return new ResultCache(getName(), CacheManager.getInstance().getCache(getName(), CacheType.Weekly)); } @Override diff --git a/source/net/filebot/web/OpenSubtitlesClient.java b/source/net/filebot/web/OpenSubtitlesClient.java index f4d075b8..2a76bdf9 100644 --- a/source/net/filebot/web/OpenSubtitlesClient.java +++ b/source/net/filebot/web/OpenSubtitlesClient.java @@ -30,6 +30,7 @@ import javax.swing.Icon; import net.filebot.Cache; import net.filebot.Cache.Key; +import net.filebot.CacheType; import net.filebot.ResourceManager; import net.filebot.media.MediaDetection; import net.filebot.mediainfo.MediaInfo; @@ -85,7 +86,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS } public ResultCache getCache() { - return new ResultCache(getName(), Cache.getCache("web-datasource")); + return new ResultCache(getName(), Cache.getCache(getName(), CacheType.Daily)); } @Override @@ -603,7 +604,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS */ @SuppressWarnings("unchecked") protected synchronized Map getSubLanguageMap() throws IOException { - Cache cache = Cache.getCache("web-datasource-lv2"); + Cache cache = Cache.getCache(getName(), CacheType.Persistent); String cacheKey = getClass().getName() + ".subLanguageMap"; // try to get language map from cache diff --git a/source/net/filebot/web/ShooterSubtitles.java b/source/net/filebot/web/ShooterSubtitles.java index dcee8276..7d81a606 100644 --- a/source/net/filebot/web/ShooterSubtitles.java +++ b/source/net/filebot/web/ShooterSubtitles.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.swing.Icon; import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.ResourceManager; import org.json.simple.JSONArray; @@ -88,7 +89,7 @@ public class ShooterSubtitles implements VideoHashSubtitleService { param.put("format", "json"); param.put("lang", LANGUAGE_CHINESE.equals(languageName) ? "Chn" : "Eng"); - Cache cache = Cache.getCache("web-datasource"); + Cache cache = Cache.getCache(getName(), CacheType.Daily); String key = endpoint.toString() + param.toString(); SubtitleDescriptor[] value = cache.get(key, SubtitleDescriptor[].class); if (value != null) { diff --git a/source/net/filebot/web/TVMazeClient.java b/source/net/filebot/web/TVMazeClient.java index f66eca2b..1bb6c68e 100644 --- a/source/net/filebot/web/TVMazeClient.java +++ b/source/net/filebot/web/TVMazeClient.java @@ -16,6 +16,7 @@ import java.util.Objects; import javax.swing.Icon; import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.ResourceManager; public class TVMazeClient extends AbstractEpisodeListProvider { @@ -52,7 +53,7 @@ public class TVMazeClient extends AbstractEpisodeListProvider { @Override public ResultCache getCache() { - return new ResultCache(getName(), Cache.getCache("web-datasource")); + return new ResultCache(getName(), Cache.getCache(getName(), CacheType.Daily)); } @Override diff --git a/source/net/filebot/web/TheTVDBClient.java b/source/net/filebot/web/TheTVDBClient.java index c341e20f..7948cf80 100644 --- a/source/net/filebot/web/TheTVDBClient.java +++ b/source/net/filebot/web/TheTVDBClient.java @@ -28,6 +28,7 @@ import java.util.logging.Logger; import javax.swing.Icon; import net.filebot.Cache; +import net.filebot.CacheType; import net.filebot.ResourceManager; import net.filebot.util.FileUtilities; import net.filebot.web.TheTVDBClient.BannerDescriptor.BannerProperty; @@ -77,7 +78,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider { @Override public ResultCache getCache() { - return new ResultCache(getName(), Cache.getCache("web-datasource")); + return new ResultCache(getName(), Cache.getCache(getName(), CacheType.Daily)); } public String getLanguageCode(Locale locale) {