ANSSRegionsFactory.java

  1. package gov.usgs.earthquake.geoserve;

  2. import java.io.File;
  3. import java.io.InputStream;
  4. import java.io.IOException;
  5. import java.util.Date;
  6. import java.util.Timer;
  7. import java.util.TimerTask;
  8. import java.util.logging.Level;
  9. import java.util.logging.Logger;

  10. import javax.json.Json;
  11. import javax.json.JsonObject;

  12. import gov.usgs.earthquake.qdm.Regions;
  13. import gov.usgs.util.FileUtils;
  14. import gov.usgs.util.StreamUtils;
  15. import gov.usgs.util.XmlUtils;

  16. /**
  17.  * Class to manage ANSS Authoritative Region updates.
  18.  *
  19.  * Simplest usage:
  20.  *     ANSSRegionsFactory.getFactory().getRegions()
  21.  *
  22.  * Regions are not fetched until {@link #startup()}
  23.  * (or {@link #fetchRegions()}) is called.
  24.  */
  25. public class ANSSRegionsFactory {

  26.     /** logging object */
  27.     public static final Logger LOGGER = Logger.getLogger(ANSSRegionsFactory.class.getName());

  28.     /** milliseconds per day */
  29.     public static final long MILLISECONDS_PER_DAY = 86400000L;

  30.     /** path to write regions.json */
  31.     public static final String DEFAULT_REGIONS_JSON = "regions.json";

  32.     /** global factory object */
  33.     private static ANSSRegionsFactory SINGLETON;


  34.     /** service used to load regions */
  35.     private GeoserveLayersService geoserveLayersService;

  36.     /** path to local regions file */
  37.     private File localRegions = new File(DEFAULT_REGIONS_JSON);

  38.     /** the current regions object */
  39.     private Regions regions;

  40.     /** shutdown hook registered by startup */
  41.     private Thread shutdownHook;

  42.     /** timer used to auto fetch region updates */
  43.     private Timer updateTimer = new Timer();


  44.     /**
  45.      * Use default GeoserveLayersService.
  46.      */
  47.     public ANSSRegionsFactory () {
  48.         this(new GeoserveLayersService());
  49.     }

  50.     /**
  51.      * Use custom GeoserveLayersService.
  52.      * @param geoserveLayersService to use
  53.      */
  54.     public ANSSRegionsFactory (final GeoserveLayersService geoserveLayersService) {
  55.         this.geoserveLayersService = geoserveLayersService;
  56.     }

  57.     /**
  58.      * Get the global ANSSRegionsFactory,
  59.      * creating and starting if needed.
  60.      * @return ANSSRegionsFactory
  61.      */
  62.     public static synchronized ANSSRegionsFactory getFactory() {
  63.         return getFactory(true);
  64.     }

  65.     /**
  66.      * @param startup if Factory should be created and started, if needed
  67.      * @return ANSSRegionsFactory
  68.      */
  69.     public static synchronized ANSSRegionsFactory getFactory(final boolean startup) {
  70.         if (SINGLETON == null) {
  71.             SINGLETON = new ANSSRegionsFactory();
  72.             if (startup) {
  73.                 SINGLETON.startup();
  74.             }
  75.         }
  76.         return SINGLETON;
  77.     }

  78.     /**
  79.      * Set the global ANSSRegionsFactory,
  80.      * shutting down any existing factory if needed.
  81.      * @param factory to set
  82.      */
  83.     public static synchronized void setFactory(final ANSSRegionsFactory factory) {
  84.         if (SINGLETON != null) {
  85.             SINGLETON.shutdown();
  86.         }
  87.         SINGLETON = factory;
  88.     }

  89.     /**
  90.      * Download regions from geoserve.
  91.      *
  92.      * Writes out to "regions.json" in current working directory and,
  93.      * if unable to update, reads in local copy.
  94.      */
  95.     public void fetchRegions () {
  96.         try {
  97.             // try loading from geoserve
  98.             this.regions = loadFromGeoserve();
  99.         } catch (Exception e) {
  100.             LOGGER.log(Level.WARNING,
  101.                     "Error fetching ANSS Regions from geoserve",
  102.                     e);
  103.             try {
  104.                 if (this.regions == null) {
  105.                     // fall back to local cache
  106.                     this.regions = loadFromFile();
  107.                 }
  108.             } catch (Exception e2) {
  109.                 LOGGER.log(Level.WARNING,
  110.                         "Error fetching ANSS Regions from local file",
  111.                         e);
  112.             }
  113.         }
  114.     }

  115.     /**
  116.      * Read regions from local regions file.
  117.      * @return Regions
  118.      * @throws IOException if error occurs
  119.      */
  120.     protected Regions loadFromFile() throws IOException {
  121.         try (InputStream in = StreamUtils.getInputStream(this.localRegions)) {
  122.             JsonObject json = Json.createReader(in).readObject();
  123.             Regions regions = new RegionsJSON().parseRegions(json);
  124.             // regions loaded
  125.             LOGGER.fine("Loaded ANSS Authoritative Regions from "
  126.                     + this.localRegions
  127.                     + ", last modified=" + XmlUtils.formatDate(
  128.                             new Date(this.localRegions.lastModified())));
  129.             return regions;
  130.         }
  131.     }

  132.     /**
  133.      * Read regions from geoserve service.
  134.      * @return Regions
  135.      * @throws IOException if error occurs
  136.      */
  137.     protected Regions loadFromGeoserve() throws IOException {
  138.         LOGGER.fine("Fetching ANSS Authoritative Regions from Geoserve");
  139.         JsonObject json = this.geoserveLayersService.getLayer("anss");
  140.         Regions regions = new RegionsJSON().parseRegions(json);
  141.         LOGGER.finer("Loaded ANSS Authoritative Regions from Geoserve");
  142.         try {
  143.             saveToFile(this.localRegions, json);
  144.         } catch (IOException e) {
  145.             // log for now, since saving is value added
  146.             LOGGER.log(Level.INFO, "Error saving local regions", e);
  147.         }
  148.         return regions;
  149.     }

  150.     /**
  151.      * Store json to local regions file.
  152.      *
  153.      * @param regionsFile to store to
  154.      * @param json json response to store locally.
  155.      * @throws IOException if IO error occurs
  156.      */
  157.     protected void saveToFile(final File regionsFile, final JsonObject json) throws IOException {
  158.         LOGGER.fine("Storing ANSS Authoritative Regions to " + regionsFile);
  159.         // save regions if needed later
  160.         FileUtils.writeFileThenMove(
  161.                 new File(regionsFile.toString() + ".temp"),
  162.                 regionsFile,
  163.                 json.toString().getBytes());
  164.         LOGGER.finer("Stored ANSS Regions to " + regionsFile);
  165.     }

  166.     /**
  167.      * Start updating regions.
  168.      */
  169.     public void startup() {
  170.         if (this.shutdownHook != null) {
  171.             // already started
  172.             return;
  173.         }

  174.         // do initial fetch
  175.         fetchRegions();

  176.         // schedule periodic fetch
  177.         long now = new Date().getTime();
  178.         long nextMidnight = MILLISECONDS_PER_DAY - (now % MILLISECONDS_PER_DAY);
  179.         updateTimer.scheduleAtFixedRate(
  180.                 new TimerTask() {
  181.                     @Override
  182.                     public void run() {
  183.                         fetchRegions();
  184.                     }
  185.                 },
  186.                 // firstt time at midnight
  187.                 nextMidnight,
  188.                 // once per day
  189.                 MILLISECONDS_PER_DAY);

  190.         // register shutdown hook
  191.         this.shutdownHook = new Thread(() -> {
  192.             // stop periodic fetch
  193.             this.updateTimer.cancel();
  194.         });
  195.         Runtime.getRuntime().addShutdownHook(this.shutdownHook);
  196.     }

  197.     /**
  198.      * Stop updating regions.
  199.      */
  200.     public void shutdown() {
  201.         if (this.shutdownHook == null) {
  202.             // not started or already stopped
  203.             return;
  204.         }

  205.         // stop periodic fetch
  206.         this.updateTimer.cancel();

  207.         // remove shutdown hook
  208.         Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
  209.         this.shutdownHook = null;
  210.     }

  211.     /**
  212.      * Get the service.
  213.      * @return geoserveLayersService
  214.      */
  215.     public GeoserveLayersService getGeoserveLayersService() {
  216.         return this.geoserveLayersService;
  217.     }

  218.     /**
  219.      * Set the service.
  220.      * @param service GeoserveLayersService to set
  221.      */
  222.     public void setGeoserveLayersService(final GeoserveLayersService service) {
  223.         this.geoserveLayersService = service;
  224.     }

  225.     /**
  226.      * Get the local regions file.
  227.      * @return localRegions
  228.      */
  229.     public File getLocalRegions() {
  230.         return this.localRegions;
  231.     }

  232.     /**
  233.      * Set the local regions file.
  234.      * @param localRegions file to set
  235.      */
  236.     public void setLocalRegions(final File localRegions) {
  237.         this.localRegions = localRegions;
  238.     }

  239.     /**
  240.      * Get the most recently fetched Regions.
  241.      * @return regions
  242.      */
  243.     public Regions getRegions () {
  244.         return this.regions;
  245.     }

  246. }