diff --git a/dss-service/src/main/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoader.java b/dss-service/src/main/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoader.java index 483891953c..ebfcb02a36 100644 --- a/dss-service/src/main/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoader.java +++ b/dss-service/src/main/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoader.java @@ -24,6 +24,7 @@ import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.DSSException; import eu.europa.esig.dss.model.FileDocument; +import eu.europa.esig.dss.model.InMemoryDocument; import eu.europa.esig.dss.spi.DSSUtils; import eu.europa.esig.dss.spi.client.http.DSSCacheFileLoader; import eu.europa.esig.dss.spi.client.http.DataLoader; @@ -37,12 +38,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.function.Predicate; /** * This class provides some caching features to handle the resources. The default cache folder is set to @@ -70,12 +67,20 @@ public class FileCacheDataLoader implements DataLoader, DSSCacheFileLoader { /** List of URIs to be ignored */ private List toIgnored; + /** List of conditions to be passed by data */ + private List> cacheConditions; + /** The cache expiration time, after which the document shall be downloaded again */ private long cacheExpirationTime = -1; /** The dataloader to be used for a remote files access */ private DataLoader dataLoader; + /** The dataloader to be used as a fallback, when the data returned by {@link FileCacheDataLoader#dataLoader} + * does not pass the {@link FileCacheDataLoader#cacheConditions} + * */ + private DataLoader fallbackDataLoader; + /** * Empty constructor */ @@ -110,6 +115,23 @@ public void setDataLoader(DataLoader dataLoader) { this.dataLoader = dataLoader; } + /** + * Gets the fallback data loader + * + * @return {@link DataLoader} + */ + public DataLoader getFallbackDataLoader() { + return fallbackDataLoader; + } + + /** + * Sets the fallback data loader + * + * @param fallbackDataLoader {@link DataLoader} + */ + public void setFallbackDataLoader(DataLoader fallbackDataLoader) { + this.fallbackDataLoader = fallbackDataLoader; + } /** * This method allows to set the file cache directory. If the cache folder does not exist then it's created. * @@ -193,6 +215,21 @@ public void addToBeIgnored(final String urlString) { } } + /** + * This method allows setting predicates, that the data returned by {@link FileCacheDataLoader#dataLoader} must + * pass in order to be cached and returned. + * + * @param cacheCondition + * the predicate to be tested with the returned data + */ + public void addCacheCondition(final Predicate cacheCondition) { + if (cacheConditions == null) { + + cacheConditions = new ArrayList<>(); + } + cacheConditions.add(cacheCondition); + } + /** * Executes a GET request to the provided URL, with a forced cache {@code refresh} when defined * @@ -251,14 +288,32 @@ public DSSDocument getDocument(final String url, final boolean refresh) { bytes = dataLoader.get(url); } - - if (Utils.isArrayNotEmpty(bytes)) { + + if (Utils.isArrayEmpty(bytes)) { + throw new DSSExternalResourceException(String.format("Cannot retrieve data from url [%s]. Empty content is obtained!", url)); + } + if (shouldBeCached(bytes)) { final File out = createFile(fileName, bytes); return new FileDocument(out); - - } - throw new DSSExternalResourceException(String.format("Cannot retrieve data from url [%s]. Empty content is obtained!", url)); - + } + if (fileExists) { + LOG.warn("Data retrieved from url [{}] did not pass cache conditions! Returning earlier cached data", url); + return new FileDocument(file); + } + if (fallbackDataLoader != null) { + LOG.warn("Data retrieved from url [{}] did not pass cache conditions! Returning data from fallback data loader", url); + return new InMemoryDocument(fallbackDataLoader.get(url)); + } + throw new DSSExternalResourceException(String.format("Data retrieved from url [%s] did not pass cache conditions!", url)); + + } + + private boolean shouldBeCached(byte[] bytes) { + if (cacheConditions == null || cacheConditions.isEmpty()) { + return true; + } + return cacheConditions.stream() + .allMatch(cond -> cond.test(bytes)); } @Override @@ -383,12 +438,23 @@ public byte[] post(final String urlString, final byte[] content) throws DSSExcep returnedBytes = dataLoader.post(urlString, content); } - if (Utils.isArrayNotEmpty(returnedBytes)) { + if (Utils.isArrayEmpty(returnedBytes)) { + throw new DSSExternalResourceException(String.format("Cannot retrieve data from url [%s]. Empty content is obtained!", urlString)); + } + if (shouldBeCached(returnedBytes)) { final File cacheFile = getCacheFile(cacheFileName); DSSUtils.saveToFile(returnedBytes, cacheFile); return returnedBytes; } - throw new DSSExternalResourceException(String.format("Cannot retrieve data from URL [%s]", urlString)); + if (fileExists) { + LOG.warn("Data retrieved from url [{}] did not pass cache conditions! Returning earlier cached data", urlString); + return DSSUtils.toByteArray(file); + } + if (fallbackDataLoader != null) { + LOG.warn("Data retrieved from url [{}] did not pass cache conditions! Returning data from fallback data loader", urlString); + return fallbackDataLoader.get(urlString); + } + throw new DSSExternalResourceException(String.format("Data retrieved from url [%s] did not pass cache conditions!", urlString)); } private boolean isCacheExpired(File file) { diff --git a/dss-service/src/test/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoaderTest.java b/dss-service/src/test/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoaderTest.java index 706bd2b279..68655855c1 100644 --- a/dss-service/src/test/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoaderTest.java +++ b/dss-service/src/test/java/eu/europa/esig/dss/service/http/commons/FileCacheDataLoaderTest.java @@ -23,9 +23,11 @@ import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.DSSException; import eu.europa.esig.dss.spi.DSSUtils; +import eu.europa.esig.dss.spi.client.http.DataLoader; import eu.europa.esig.dss.spi.client.http.DataLoader.DataAndUrl; import eu.europa.esig.dss.spi.client.http.IgnoreDataLoader; import eu.europa.esig.dss.spi.client.http.MemoryDataLoader; +import eu.europa.esig.dss.spi.exception.DSSExternalResourceException; import eu.europa.esig.dss.utils.Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,10 +45,7 @@ import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class FileCacheDataLoaderTest { @@ -179,6 +178,25 @@ void offlineDataLoaderTest() throws IOException { assertNotNull(dataAndUrl.getData()); } + @Test + void testCacheConditions() { + dataLoader.addCacheCondition(it -> false); + assertThrows(DSSExternalResourceException.class, () -> dataLoader.get(URL_TO_LOAD)); + assertNull(getCachedFile(cacheDirectory)); + } + + @Test + void testFallbackDataLoader() { + Map dataMap = new HashMap<>(); + dataMap.put(URL_TO_LOAD, URL_TO_LOAD.getBytes()); + DataLoader fallbackDataLoader = new MemoryDataLoader(dataMap); + dataLoader.addCacheCondition(it -> false); + dataLoader.setFallbackDataLoader(fallbackDataLoader); + + assertArrayEquals(URL_TO_LOAD.getBytes(), dataLoader.get(URL_TO_LOAD)); + assertNull(getCachedFile(cacheDirectory)); + } + private long getUrlAndReturnCacheCreationTime() { byte[] bytesArray = dataLoader.get(URL_TO_LOAD); assertTrue(bytesArray.length > 0);