/* * DSS - Digital Signature Services * * Copyright (C) 2011 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel * * Developed by: 2011 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" is free software: you can redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the * License, or (at your option) any later version. * * DSS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.validation102853.https; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.DigestAlgorithm; import eu.europa.ec.markt.dss.ResourceLoader; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.validation102853.loader.Protocol; /** * This class provides some caching features to handle the resources. The default cache folder is set to {@code java.io.tmpdir}. The urls of the resources is transformed to the * file name by replacing the special characters by {@code _} */ public class FileCacheDataLoader extends CommonDataLoader { private static final Logger LOG = LoggerFactory.getLogger(FileCacheDataLoader.class); private File fileCacheDirectory = new File(System.getProperty("java.io.tmpdir")); private ResourceLoader resourceLoader = new ResourceLoader(); private List<String> toBeLoaded; private List<String> toIgnored; /** * This method allows to set the file cache directory. If the cache folder does not exists then it's created. * * @param fileCacheDirectory {@code File} pointing the cache folder to be used. */ public void setFileCacheDirectory(final File fileCacheDirectory) { this.fileCacheDirectory = fileCacheDirectory; this.fileCacheDirectory.mkdirs(); } public void setResourceLoader(final ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } /** * This methods allows to indicate if the resource must be obtained. If this method has been invoked then only the provided URL will be processed. * * @param url to be processed */ public void addToBeLoaded(final String url) { if (toBeLoaded == null) { toBeLoaded = new ArrayList<String>(); } if (DSSUtils.isNotBlank(url)) { toBeLoaded.add(url); } } /** * This methods allows to indicate which resources must be ignored. It is useful in a test environment where some of fake sources a not available. It prevents to wait for the * timeout. * * @param urlString to be ignored. It can be the original URL or the cache file name */ public void addToBeIgnored(final String urlString) { if (toIgnored == null) { toIgnored = new ArrayList<String>(); } if (DSSUtils.isNotBlank(urlString)) { final String normalizedFileName = ResourceLoader.getNormalizedFileName(urlString); toIgnored.add(normalizedFileName); } } @Override public byte[] get(final String url, final boolean refresh) throws DSSException { if (toBeLoaded != null && !toBeLoaded.contains(url)) { return null; } final String cacheFileName = ResourceLoader.getNormalizedFileName(url); final byte[] cachedFileContent = getCachedFileContent(cacheFileName, refresh); if (cachedFileContent != null) { return cachedFileContent; } final byte[] returnedBytes; if (!isNetworkProtocol(url)) { returnedBytes = getContentUsingNotNetworkProtocol(url); } else { returnedBytes = super.get(url); } if (returnedBytes != null && returnedBytes.length != 0) { final File out = getCacheFile(cacheFileName); DSSUtils.saveToFile(returnedBytes, out); } return returnedBytes; } private byte[] getContentUsingNotNetworkProtocol(String url) { final String resourcePath = resourceLoader.getAbsoluteResourceFolder(url.trim()); final File fileResource = new File(resourcePath); final byte[] bytes = DSSUtils.toByteArray(fileResource); return bytes; } private byte[] getCachedFileContent(final String cacheFileName, final boolean refresh) { final File file = getCacheFile(cacheFileName); final boolean fileExists = file.exists(); if (fileExists && !refresh) { LOG.debug("Cached file was used"); final byte[] bytes = DSSUtils.toByteArray(file); return bytes; } else { if (!fileExists) { LOG.debug("There is no cached file!"); } else { LOG.debug("The refresh is forced!"); } } return null; } @Override public byte[] get(final String url) throws DSSException { return get(url, false); } protected boolean isNetworkProtocol(final String urlString) { final String normalizedUrl = urlString.trim().toLowerCase(); return Protocol.isHttpUrl(normalizedUrl) || Protocol.isLdapUrl(normalizedUrl) || Protocol.isFtpUrl(normalizedUrl); } private File getCacheFile(final String fileName) { final String trimmedFileName = fileName.trim(); if (toIgnored != null && toIgnored.contains(trimmedFileName)) { throw new DSSException("Part of urls to ignore."); } LOG.debug("Cached file: " + fileCacheDirectory + "/" + trimmedFileName); final File file = new File(fileCacheDirectory, trimmedFileName); return file; } /** * Allows to load the file for a given file name from the cache folder. * * @return the content of the file or {@code null} if the file does not exist */ public byte[] loadFileFromCache(final String urlString) { final String fileName = ResourceLoader.getNormalizedFileName(urlString); final File file = getCacheFile(fileName); if (file.exists()) { final byte[] bytes = DSSUtils.toByteArray(file); return bytes; } return null; } /** * Allows to add a given array of {@code byte} as a cache file representing by the {@code urlString}. * * @param urlString the URL to add to the cache * @param bytes the content of the cache file */ public void saveBytesInCache(final String urlString, final byte[] bytes) { final String fileName = ResourceLoader.getNormalizedFileName(urlString); final File out = getCacheFile(fileName); DSSUtils.saveToFile(bytes, out); } // TODO-Bob (22/02/2015): request id should be added (or something like this) to cope with nonce extension of the OCSP for example... @Override public byte[] post(final String urlString, final byte[] requestBytes, boolean refresh) throws DSSException { final String fileName = ResourceLoader.getNormalizedFileName(urlString); final byte[] digest = DSSUtils.digest(DigestAlgorithm.MD5, requestBytes); final String digestHexEncoded = DSSUtils.toHex(digest); final String cacheFileName = fileName + "." + digestHexEncoded; final byte[] cachedFileContent = getCachedFileContent(cacheFileName, refresh); if (cachedFileContent != null) { return cachedFileContent; } if (!isNetworkProtocol(urlString)) { return getContentUsingNotNetworkProtocol(urlString); } final byte[] returnedBytes = super.post(urlString, requestBytes); if (returnedBytes.length != 0) { final File cacheFile = getCacheFile(cacheFileName); DSSUtils.saveToFile(returnedBytes, cacheFile); } return returnedBytes; } @Override public byte[] post(final String urlString, final byte[] requestBytes) throws DSSException { return post(urlString, requestBytes, false); } public List<String> getToBeLoaded() { return toBeLoaded == null ? null : Collections.unmodifiableList(toBeLoaded); } public List<String> getToIgnored() { return toIgnored == null ? null : Collections.unmodifiableList(toIgnored); } }