CachingXmlDataStore.java
- /*******************************************************************************
- * Copyright 2012 André Rouél
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- ******************************************************************************/
- package net.sf.uadetector.datastore;
- import java.io.File;
- import java.io.IOException;
- import java.net.URL;
- import java.nio.charset.Charset;
- import javax.annotation.Nonnull;
- import net.sf.qualitycheck.Check;
- import net.sf.qualitycheck.exception.IllegalStateOfArgumentException;
- import net.sf.uadetector.datareader.DataReader;
- import net.sf.uadetector.datareader.XmlDataReader;
- import net.sf.uadetector.internal.data.Data;
- import net.sf.uadetector.internal.util.FileUtil;
- import net.sf.uadetector.internal.util.UrlUtil;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * Implementation of a {@link DataStore} which is able to recover <em>UAS data</em> in XML format from a cache file. If
- * the cache file is empty, the data will be read from the given data URL.<br>
- * <br>
- * You can also update the data of the store at any time if you trigger {@link CachingXmlDataStore#refresh()}.
- *
- * @author André Rouél
- */
- public final class CachingXmlDataStore extends AbstractRefreshableDataStore {
- /**
- * Internal data store which will be used to load previously saved <em>UAS data</em> from a cache file.
- */
- private static class CacheFileDataStore extends AbstractDataStore {
- protected CacheFileDataStore(final Data data, final DataReader reader, final URL dataUrl, final Charset charset) {
- super(data, reader, dataUrl, dataUrl, charset);
- }
- }
- /**
- * The default temporary-file directory
- */
- private static final String CACHE_DIR = System.getProperty("java.io.tmpdir");
- /**
- * Corresponding default logger of this class
- */
- private static final Logger LOG = LoggerFactory.getLogger(CachingXmlDataStore.class);
- /**
- * Message for the log if the cache file is filled
- */
- private static final String MSG_CACHE_FILE_IS_EMPTY = "The cache file is empty. The given UAS data source will be imported.";
- /**
- * Message for the log if the cache file is empty
- */
- private static final String MSG_CACHE_FILE_IS_FILLED = "The cache file is filled and will be imported.";
- /**
- * Message if the cache file contains unexpected data and must be deleted manually
- */
- private static final String MSG_CACHE_FILE_IS_DAMAGED = "The cache file '%s' is damaged and must be removed manually.";
- /**
- * Message if the cache file contains unexpected data and has been removed
- */
- private static final String MSG_CACHE_FILE_IS_DAMAGED_AND_DELETED = "The cache file '%s' is damaged and has been deleted.";
- /**
- * The prefix string to be used in generating the cache file's name; must be at least three characters long
- */
- private static final String PREFIX = "uas";
- /**
- * The suffix string to be used in generating the cache file's name; may be {@code null}, in which case the suffix "
- * {@code .tmp}" will be used
- */
- private static final String SUFFIX = ".xml";
- /**
- * Constructs a new instance of {@code CachingXmlDataStore} with the given arguments. The given {@code cacheFile}
- * can be empty or filled with previously cached data in XML format. The file must be writable otherwise an
- * exception will be thrown.
- *
- * @param fallback
- * <em>UAS data</em> as fallback in case the data on the specified resource can not be read correctly
- * @return new instance of {@link CachingXmlDataStore}
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if one of the given arguments is {@code null}
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if the given cache file can not be read
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if no URL can be resolved to the given given file
- */
- @Nonnull
- public static CachingXmlDataStore createCachingXmlDataStore(@Nonnull final DataStore fallback) {
- return createCachingXmlDataStore(findOrCreateCacheFile(), fallback);
- }
- /**
- * Constructs a new instance of {@code CachingXmlDataStore} with the given arguments. The given {@code cacheFile}
- * can be empty or filled with previously cached data in XML format. The file must be writable otherwise an
- * exception will be thrown.
- *
- * @param cacheFile
- * file with cached <em>UAS data</em> in XML format or empty file
- * @param fallback
- * <em>UAS data</em> as fallback in case the data on the specified resource can not be read correctly
- * @return new instance of {@link CachingXmlDataStore}
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if one of the given arguments is {@code null}
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if the given cache file can not be read
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if no URL can be resolved to the given given file
- */
- @Nonnull
- public static CachingXmlDataStore createCachingXmlDataStore(@Nonnull final File cacheFile, @Nonnull final DataStore fallback) {
- return createCachingXmlDataStore(cacheFile, UrlUtil.build(DEFAULT_DATA_URL), UrlUtil.build(DEFAULT_VERSION_URL), DEFAULT_CHARSET,
- fallback);
- }
- /**
- * Constructs a new instance of {@code CachingXmlDataStore} with the given arguments. The given {@code cacheFile}
- * can be empty or filled with previously cached data in XML format. The file must be writable otherwise an
- * exception will be thrown.
- *
- * @param cacheFile
- * file with cached <em>UAS data</em> in XML format or empty file
- * @param dataUrl
- * URL to <em>UAS data</em>
- * @param versionUrl
- * URL to version information about the given <em>UAS data</em>
- * @param charset
- * the character set in which the data should be read
- * @param fallback
- * <em>UAS data</em> as fallback in case the data on the specified resource can not be read correctly
- * @return new instance of {@link CachingXmlDataStore}
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if one of the given arguments is {@code null}
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if the given cache file can not be read
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if no URL can be resolved to the given given file
- */
- @Nonnull
- public static CachingXmlDataStore createCachingXmlDataStore(@Nonnull final File cacheFile, @Nonnull final URL dataUrl,
- @Nonnull final URL versionUrl, @Nonnull final Charset charset, @Nonnull final DataStore fallback) {
- Check.notNull(cacheFile, "cacheFile");
- Check.notNull(charset, "charset");
- Check.notNull(dataUrl, "dataUrl");
- Check.notNull(fallback, "fallback");
- Check.notNull(versionUrl, "versionUrl");
- final DataReader reader = new XmlDataReader();
- final DataStore fallbackDataStore = readCacheFileAsFallback(reader, cacheFile, charset, fallback);
- return new CachingXmlDataStore(reader, dataUrl, versionUrl, charset, cacheFile, fallbackDataStore);
- }
- /**
- * Constructs a new instance of {@code CachingXmlDataStore} with the given arguments. The file used to cache the
- * read in <em>UAS data</em> will be called from {@link CachingXmlDataStore#findOrCreateCacheFile()}. This file may
- * be empty or filled with previously cached data in XML format. The file must be writable otherwise an exception
- * will be thrown.
- *
- * @param dataUrl
- * URL to <em>UAS data</em>
- * @param versionUrl
- * URL to version information about the given <em>UAS data</em>
- * @param charset
- * the character set in which the data should be read
- * @param fallback
- * <em>UAS data</em> as fallback in case the data on the specified resource can not be read correctly
- * @return new instance of {@link CachingXmlDataStore}
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if one of the given arguments is {@code null}
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if the given cache file can not be read
- */
- @Nonnull
- public static CachingXmlDataStore createCachingXmlDataStore(@Nonnull final URL dataUrl, @Nonnull final URL versionUrl,
- @Nonnull final Charset charset, @Nonnull final DataStore fallback) {
- return createCachingXmlDataStore(findOrCreateCacheFile(), dataUrl, versionUrl, charset, fallback);
- }
- /**
- * Removes the given cache file because it contains damaged content.
- *
- * @param cacheFile
- * cache file to delete
- */
- private static void deleteCacheFile(final File cacheFile) {
- try {
- if (cacheFile.delete()) {
- LOG.warn(String.format(MSG_CACHE_FILE_IS_DAMAGED_AND_DELETED, cacheFile.getPath()));
- } else {
- LOG.warn(String.format(MSG_CACHE_FILE_IS_DAMAGED, cacheFile.getPath()));
- }
- } catch (final Exception e) {
- LOG.warn(String.format(MSG_CACHE_FILE_IS_DAMAGED, cacheFile.getPath()));
- }
- }
- /**
- * Gets the cache file for <em>UAS data</em> in the default temporary-file directory. If no cache file exists, a new
- * empty file in the default temporary-file directory will be created, using the default prefix and suffix to
- * generate its name.
- *
- * @return file to cache read in <em>UAS data</em>
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if the cache file can not be created
- */
- @Nonnull
- public static File findOrCreateCacheFile() {
- final File file = new File(CACHE_DIR, PREFIX + SUFFIX);
- if (!file.exists()) {
- try {
- file.createNewFile();
- } catch (final IOException e) {
- throw new IllegalStateOfArgumentException("Can not create a cache file.", e);
- }
- }
- return file;
- }
- /**
- * Checks if the given file is empty.
- *
- * @param file
- * the file that could be empty
- * @return {@code true} when the file is accessible and empty otherwise {@code false}
- * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
- * if an I/O error occurs
- */
- private static boolean isEmpty(@Nonnull final File file, @Nonnull final Charset charset) {
- try {
- return FileUtil.isEmpty(file, charset);
- } catch (final IOException e) {
- throw new IllegalStateOfArgumentException("The given file could not be read.", e);
- }
- }
- /**
- * Tries to read the content of specified cache file and returns them as fallback data store. If the cache file
- * contains unexpected data the given fallback data store will be returned instead.
- *
- * @param reader
- * data reader to read the given {@code dataUrl}
- * @param cacheFile
- * file with cached <em>UAS data</em> in XML format or empty file
- * @param versionUrl
- * URL to version information about the given <em>UAS data</em>
- * @param charset
- * the character set in which the data should be read
- * @param fallback
- * <em>UAS data</em> as fallback in case the data on the specified resource can not be read correctly
- * @return a fallback data store
- */
- private static DataStore readCacheFileAsFallback(@Nonnull final DataReader reader, @Nonnull final File cacheFile,
- @Nonnull final Charset charset, @Nonnull final DataStore fallback) {
- DataStore fallbackDataStore;
- if (!isEmpty(cacheFile, charset)) {
- final URL cacheFileUrl = UrlUtil.toUrl(cacheFile);
- try {
- fallbackDataStore = new CacheFileDataStore(reader.read(cacheFileUrl, charset), reader, cacheFileUrl, charset);
- LOG.debug(MSG_CACHE_FILE_IS_FILLED);
- } catch (final RuntimeException e) {
- fallbackDataStore = fallback;
- deleteCacheFile(cacheFile);
- }
- } else {
- fallbackDataStore = fallback;
- LOG.debug(MSG_CACHE_FILE_IS_EMPTY);
- }
- return fallbackDataStore;
- }
- /**
- * Constructs an {@code CachingXmlDataStore} with the given arguments.
- *
- * @param data
- * first <em>UAS data</em> which will be available in the store
- * @param reader
- * data reader to read the given {@code dataUrl}
- * @param dataUrl
- * URL to <em>UAS data</em>
- * @param versionUrl
- * URL to version information about the given <em>UAS data</em>
- * @param charset
- * the character set in which the data should be read
- * @param cacheFile
- * file with cached <em>UAS data</em> in XML format or an empty file
- * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
- * if one of the given arguments is {@code null}
- */
- private CachingXmlDataStore(@Nonnull final DataReader reader, @Nonnull final URL dataUrl, @Nonnull final URL versionUrl,
- @Nonnull final Charset charset, @Nonnull final File cacheFile, @Nonnull final DataStore fallback) {
- super(reader, dataUrl, versionUrl, charset, fallback);
- setUpdateOperation(new UpdateOperationWithCacheFileTask(this, cacheFile));
- }
- }