/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.gs.download; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.data.util.IOUtils; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.Paths; import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.Resources; import org.geoserver.security.PropertyFileWatcher; import org.geotools.util.Utilities; import org.geotools.util.logging.Logging; /** * Basic property file based {@link DownloadServiceConfigurationGenerator} implementation with ability to reload config when the file changes. If * property file is not present, a new one will be created. * * @author Simone Giannecchini, GeoSolutions */ public class DownloadServiceConfigurationWatcher extends TimerTask implements DownloadServiceConfigurationGenerator { public static final String DOWNLOAD_PROCESS_DIR = "download-process"; public static final String PROPERTYFILENAME = "download.properties"; public final static String DEFAULT_PROPERTY_PATH = Paths.path(DOWNLOAD_PROCESS_DIR, PROPERTYFILENAME); public final static Logger LOGGER = Logging .getLogger(DownloadServiceConfigurationWatcher.class); /** * {@link PropertyFileWatcher} used for loading the property file. */ private PropertyFileWatcher watcher; /** * time in seconds between successive task executions */ private long period = 60 * 2; /** * delay in seconds before task is to be executed */ private long delay = 60 * 2; /** * The new {@link DownloadServiceConfiguration} object containing the properties load from the properties file. */ private DownloadServiceConfiguration configuration = new DownloadServiceConfiguration(); /** * {@link Timer} object used for periodically watching the properties file */ private Timer timer; /** Default watches controlflow.properties */ public DownloadServiceConfigurationWatcher() { // Get the Resource loader GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); // Check if the property file is present if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Checking properties file"); } Resource properties = loader.get(PROPERTYFILENAME); // Properties file not found. A new one is copied into the GeoServer data directory if (properties == null || !Resources.exists(properties)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Properties file not found"); } try { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Copying the default properties file inside the data directory"); } // Copy the default property file into the data directory InputStream is = DownloadServiceConfigurationWatcher.class .getResourceAsStream(DEFAULT_PROPERTY_PATH); if (is != null) { IOUtils.copy(is, properties.out()); } } catch (IOException e) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, e.getMessage(), e); } } } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Properties file found"); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Loading configuration"); } // Get the Property file as a Resource Resource downloadProperties = loader.get(PROPERTYFILENAME); init(new PropertyFileWatcher(downloadProperties)); } /** * Initialization method for loading the {@link DownloadServiceConfiguration}. * * @param propertyFileWatcher Watcher of the property file */ private void init(PropertyFileWatcher propertyFileWatcher) { Utilities.ensureNonNull("propertyFileWatcher", propertyFileWatcher); // Loading configuration from the file this.watcher = propertyFileWatcher; DownloadServiceConfiguration newConfiguration = loadConfiguration(); if (newConfiguration != null) { configuration = newConfiguration; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("New configuration loaded:\n" + configuration); } } // start background checks timer = new Timer(true); timer.scheduleAtFixedRate(this, delay * 1000, period * 1000); } /** * Loads the configuration from disk. * * @return an instance of {@link DownloadServiceConfiguration}. */ private DownloadServiceConfiguration loadConfiguration() { // load download Process Properties final Resource file = watcher.getResource(); DownloadServiceConfiguration newConfiguration = null; try { if (Resources.exists(file) && Resources.canRead(file)) { // load contents Properties properties = watcher.getProperties(); // parse contents newConfiguration = parseConfigurationValues(properties); } else { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Unable to read confguration file for download service: " + file.path() + " continuing with default configuration-->\n" + configuration); } } } catch (Exception e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Unable to read confguration file for download service: " + file.path() + " continuing with default configuration-->\n" + configuration); } } // return return newConfiguration; } /** * Parses the properties file for the download process configuration. When it runs into problems it uses default values * * @param downloadProcessProperties the {@link Properties} file to parse. Cannot be null. * @return an instance of {@link DownloadServiceConfiguration}. */ private DownloadServiceConfiguration parseConfigurationValues( Properties downloadProcessProperties) { Utilities.ensureNonNull("downloadProcessProperties", downloadProcessProperties); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Parsing the properties file"); } // Initialize the configuration fields with default values long maxFeatures = DownloadServiceConfiguration.DEFAULT_MAX_FEATURES; long rasterSizeLimits = DownloadServiceConfiguration.DEFAULT_RASTER_SIZE_LIMITS; long writeLimits = DownloadServiceConfiguration.DEFAULT_RASTER_SIZE_LIMITS; long hardOutputLimit = DownloadServiceConfiguration.DEFAULT_WRITE_LIMITS; int compressionLevel = DownloadServiceConfiguration.DEFAULT_COMPRESSION_LEVEL; // Extract the keyset from the property files Set<Object> properties = downloadProcessProperties.keySet(); // Iterates on the various keys in order to search for the various properies for (Object property : properties) { String prop = (String) property; // max features if (prop.equalsIgnoreCase(DownloadServiceConfiguration.MAX_FEATURES_NAME)) { // get value String value = (String) downloadProcessProperties .get(DownloadServiceConfiguration.MAX_FEATURES_NAME); // check and assign try { final long parseLong = Long.parseLong(value); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("maxFeatures parsed to " + parseLong); } if (parseLong > 0) { maxFeatures = parseLong; } } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("maxFeatures assigned to " + maxFeatures); } } // raster size limits if (prop.equalsIgnoreCase(DownloadServiceConfiguration.RASTER_SIZE_LIMITS_NAME)) { // get value String value = (String) downloadProcessProperties .get(DownloadServiceConfiguration.RASTER_SIZE_LIMITS_NAME); // check and assign try { final long parseLong = Long.parseLong(value); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("raster size limits parsed to " + parseLong); } if (parseLong > 0) { rasterSizeLimits = Long.parseLong(value); } } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("raster size limits assigned to " + rasterSizeLimits); } } // writeLimits if (prop.equalsIgnoreCase(DownloadServiceConfiguration.WRITE_LIMITS_NAME)) { // get value String value = (String) downloadProcessProperties .get(DownloadServiceConfiguration.WRITE_LIMITS_NAME); // check and assign try { final long parseLong = Long.parseLong(value); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("writeLimits parsed to " + parseLong); } if (parseLong > 0) { writeLimits = Long.parseLong(value); } } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("writeLimits assigned to " + writeLimits); } } // hardOutputLimit if (prop.equalsIgnoreCase("hardOutputLimit")) { // get value String value = (String) downloadProcessProperties.get("hardOutputLimit"); // check and assign try { final long parseLong = Long.parseLong(value); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("hardOutputLimit parsed to " + parseLong); } if (parseLong > 0) { hardOutputLimit = Long.parseLong(value); } } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("hardOutputLimit assigned to " + hardOutputLimit); } } // compressionLevel if (prop.equalsIgnoreCase("compressionLevel")) { // get value String value = (String) downloadProcessProperties.get("compressionLevel"); // check and assign try { final long parseLong = Long.parseLong(value); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("compressionLevel parsed to " + parseLong); } if (parseLong >= 0 && parseLong <= 8) { compressionLevel = Integer.parseInt(value); } } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("compressionLevel assigned to " + compressionLevel); } } } // create the configuration object return new DownloadServiceConfiguration(maxFeatures, rasterSizeLimits, writeLimits, hardOutputLimit, compressionLevel); } @Override public void run() { if (watcher.isStale()) { // reload DownloadServiceConfiguration newConfiguration = loadConfiguration(); if (newConfiguration != null) { synchronized (newConfiguration) { configuration = newConfiguration; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("New configuration loaded:\n" + configuration); } } } } } /** * Returns the {@link DownloadServiceConfiguration} instance. */ public DownloadServiceConfiguration getConfiguration() { return configuration; } /** * Stop the configuration watcher. */ public void stop() { try { timer.cancel(); } catch (Throwable t) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, t.getLocalizedMessage(), t); } } } }