UpdateOperationWithCacheFileTask.java

  1. /*******************************************************************************
  2.  * Copyright 2013 André Rouél
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  ******************************************************************************/
  16. package net.sf.uadetector.datastore;

  17. import java.io.File;
  18. import java.io.FileOutputStream;
  19. import java.io.IOException;
  20. import java.net.URL;
  21. import java.nio.charset.Charset;

  22. import javax.annotation.Nonnull;

  23. import net.sf.qualitycheck.Check;
  24. import net.sf.uadetector.exception.CanNotOpenStreamException;
  25. import net.sf.uadetector.internal.data.Data;
  26. import net.sf.uadetector.internal.util.Closeables;
  27. import net.sf.uadetector.internal.util.FileUtil;
  28. import net.sf.uadetector.internal.util.UrlUtil;

  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;

  31. final class UpdateOperationWithCacheFileTask extends AbstractUpdateOperation {

  32.     /**
  33.      * Corresponding default logger of this class
  34.      */
  35.     private static final Logger LOG = LoggerFactory.getLogger(UpdateOperationWithCacheFileTask.class);

  36.     /**
  37.      * Message for the log when issues occur during reading of or writing to the cache file.
  38.      */
  39.     private static final String MSG_CACHE_FILE_ISSUES = "Issues occured during reading of or writing to the cache file: %s";

  40.     /**
  41.      * Message for the log if the passed resources are the same and an update makes no sense
  42.      */
  43.     private static final String MSG_SAME_RESOURCES = "The passed URL and file resources are the same. An update was not performed.";

  44.     /**
  45.      * Creates a temporary file near the passed file. The name of the given one will be used and the suffix ".temp" will
  46.      * be added.
  47.      *
  48.      * @param file
  49.      *            file in which the entire contents from the given URL can be saved
  50.      * @throws IllegalStateException
  51.      *             if the file can not be deleted
  52.      */
  53.     protected static File createTemporaryFile(@Nonnull final File file) {
  54.         Check.notNull(file, "file");

  55.         final File tempFile = new File(file.getParent(), file.getName() + ".temp");

  56.         // remove orphaned temporary file
  57.         deleteFile(tempFile);

  58.         return tempFile;
  59.     }

  60.     /**
  61.      * Removes the given file.
  62.      *
  63.      * @param file
  64.      *            a file which should be deleted
  65.      *
  66.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  67.      *             if the given argument is {@code null}
  68.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  69.      *             if the file can not be deleted
  70.      */
  71.     protected static void deleteFile(@Nonnull final File file) {
  72.         Check.notNull(file, "file");
  73.         Check.stateIsTrue(!file.exists() || file.delete(), "Cannot delete file '%s'.", file.getPath());
  74.     }

  75.     /**
  76.      * Checks if the given file is empty.
  77.      *
  78.      * @param file
  79.      *            the file that could be empty
  80.      * @return {@code true} when the file is accessible and empty otherwise {@code false}
  81.      * @throws IllegalStateException
  82.      *             if an I/O error occurs
  83.      */
  84.     private static boolean isEmpty(@Nonnull final File file, @Nonnull final Charset charset) {
  85.         try {
  86.             return FileUtil.isEmpty(file, charset);
  87.         } catch (final IOException e) {
  88.             throw new IllegalStateException("The given file could not be read.");
  89.         }
  90.     }

  91.     /**
  92.      * Checks that {@code older} {@link Data} has a lower version number than the {@code newer} one.
  93.      *
  94.      * @param older
  95.      *            possibly older {@code Data}
  96.      * @param newer
  97.      *            possibly newer {@code Data}
  98.      * @return {@code true} if the {@code newer} Data is really newer, otherwise {@code false}
  99.      */
  100.     protected static boolean isNewerData(@Nonnull final Data older, @Nonnull final Data newer) {
  101.         return newer.getVersion().compareTo(older.getVersion()) > 0;
  102.     }

  103.     /**
  104.      * Reads the content from the given {@link URL} and saves it to the passed file.
  105.      *
  106.      * @param file
  107.      *            file in which the entire contents from the given URL can be saved
  108.      * @param store
  109.      *            a data store for <em>UAS data</em>
  110.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  111.      *             if any of the passed arguments is {@code null}
  112.      * @throws IOException
  113.      *             if an I/O error occurs
  114.      */
  115.     protected static void readAndSave(@Nonnull final File file, @Nonnull final DataStore store) throws IOException {
  116.         Check.notNull(file, "file");
  117.         Check.notNull(store, "store");

  118.         final URL url = store.getDataUrl();
  119.         final Charset charset = store.getCharset();

  120.         final boolean isEqual = url.toExternalForm().equals(UrlUtil.toUrl(file).toExternalForm());
  121.         if (!isEqual) {

  122.             // check if the data can be read in successfully
  123.             final String data = UrlUtil.read(url, charset);
  124.             if (Data.EMPTY.equals(store.getDataReader().read(data))) {
  125.                 throw new IllegalStateException("The read in content can not be transformed to an instance of 'Data'.");
  126.             }

  127.             final File tempFile = createTemporaryFile(file);

  128.             FileOutputStream outputStream = null;
  129.             boolean threw = true;
  130.             try {
  131.                 // write data to temporary file
  132.                 outputStream = new FileOutputStream(tempFile);
  133.                 outputStream.write(data.getBytes(charset));

  134.                 // delete the original file
  135.                 deleteFile(file);

  136.                 threw = false;
  137.             } finally {
  138.                 Closeables.close(outputStream, threw);
  139.             }

  140.             // rename the new file to the original one
  141.             renameFile(tempFile, file);
  142.         } else {
  143.             LOG.debug(MSG_SAME_RESOURCES);
  144.         }
  145.     }

  146.     /**
  147.      * Renames the given file {@code from} to the new file {@code to}.
  148.      *
  149.      * @param from
  150.      *            an existing file
  151.      * @param to
  152.      *            a new file
  153.      *
  154.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  155.      *             if one of the given arguments is {@code null}
  156.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  157.      *             if the file can not be renamed
  158.      */
  159.     protected static void renameFile(@Nonnull final File from, @Nonnull final File to) {
  160.         Check.notNull(from, "from");
  161.         Check.stateIsTrue(from.exists(), "Argument 'from' must not be an existing file.");
  162.         Check.notNull(to, "to");
  163.         Check.stateIsTrue(from.renameTo(to), "Renaming file from '%s' to '%s' failed.", from.getAbsolutePath(), to.getAbsolutePath());
  164.     }

  165.     /**
  166.      * File to cache read in <em>UAS data</em>
  167.      */
  168.     private final File cacheFile;

  169.     /**
  170.      * The data store for instances that implements {@link net.sf.uadetector.internal.data.Data}
  171.      */
  172.     private final AbstractRefreshableDataStore store;

  173.     public UpdateOperationWithCacheFileTask(@Nonnull final AbstractRefreshableDataStore dataStore, @Nonnull final File cacheFile) {
  174.         super(dataStore);
  175.         Check.notNull(dataStore, "dataStore");
  176.         Check.notNull(cacheFile, "cacheFile");
  177.         store = dataStore;
  178.         this.cacheFile = cacheFile;
  179.     }

  180.     @Override
  181.     public void call() {
  182.         readDataIfNewerAvailable();
  183.     }

  184.     private boolean isCacheFileEmpty() {
  185.         return isEmpty(cacheFile, store.getCharset());
  186.     }

  187.     private void readDataIfNewerAvailable() {
  188.         try {
  189.             if (isUpdateAvailable() || isCacheFileEmpty()) {
  190.                 readAndSave(cacheFile, store);
  191.                 store.setData(store.getDataReader().read(cacheFile.toURI().toURL(), store.getCharset()));
  192.             }
  193.         } catch (final CanNotOpenStreamException e) {
  194.             LOG.warn(String.format(RefreshableDataStore.MSG_URL_NOT_READABLE, e.getLocalizedMessage()));
  195.             readFallbackData();
  196.         } catch (final RuntimeException e) {
  197.             LOG.warn(RefreshableDataStore.MSG_FAULTY_CONTENT, e);
  198.             readFallbackData();
  199.         } catch (final IOException e) {
  200.             LOG.warn(String.format(MSG_CACHE_FILE_ISSUES, e.getLocalizedMessage()), e);
  201.             readFallbackData();
  202.         }
  203.     }

  204.     private void readFallbackData() {
  205.         LOG.info("Reading fallback data...");
  206.         try {
  207.             if (isCacheFileEmpty()) {
  208.                 readAndSave(cacheFile, store.getFallback());
  209.                 final Data data = store.getDataReader().read(cacheFile.toURI().toURL(), store.getCharset());
  210.                 if (isNewerData(store.getData(), data)) {
  211.                     store.setData(data);
  212.                 }
  213.             }
  214.         } catch (final CanNotOpenStreamException e) {
  215.             LOG.warn(String.format(RefreshableDataStore.MSG_URL_NOT_READABLE, e.getLocalizedMessage()));
  216.         } catch (final RuntimeException e) {
  217.             LOG.warn(RefreshableDataStore.MSG_FAULTY_CONTENT, e);
  218.         } catch (final IOException e) {
  219.             LOG.warn(String.format(MSG_CACHE_FILE_ISSUES, e.getLocalizedMessage()), e);
  220.         }
  221.     }

  222. }