/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.ResourceStore;
import org.geoserver.platform.resource.Resources;
import org.geoserver.util.Filter;
import org.geoserver.util.IOUtils;
import org.geowebcache.config.ConfigurationException;
import org.geowebcache.config.ConfigurationResourceProvider;
import org.geowebcache.config.XMLFileResourceProvider;
import org.geowebcache.storage.DefaultStorageFinder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
public class GeoserverXMLResourceProvider implements ConfigurationResourceProvider {
private static Log LOGGER = LogFactory.getLog(GeoserverXMLResourceProvider.class);
static final String GEOWEBCACHE_CONFIG_DIR_PROPERTY = XMLFileResourceProvider.GWC_CONFIG_DIR_VAR;
static final String GEOWEBCACHE_CACHE_DIR_PROPERTY = DefaultStorageFinder.GWC_CACHE_DIR;
public static final String DEFAULT_CONFIGURATION_DIR_NAME = "gwc";
/**
* Location of the configuration file
*/
private final Resource configDirectory;
/**
* Name of the configuration file
*/
private final String configFileName;
private String templateLocation;
public GeoserverXMLResourceProvider(String providedConfigDirectory, String configFileName,
ResourceStore resourceStore) throws ConfigurationException {
this.configFileName = configFileName;
this.configDirectory = inferConfigDirectory(resourceStore, providedConfigDirectory);
LOGGER.info(String.format(
"Will look for '%s' in directory '%s'.",
configFileName, configDirectory.dir().getAbsolutePath()));
}
public GeoserverXMLResourceProvider(final String configFileName,
final ResourceStore resourceStore) throws ConfigurationException {
this(null, configFileName, resourceStore);
}
/**
* Helper method that infers the directory that contains or will contain GWC configuration. First we will
* check if a specific location was set using properties GEOWEBCACHE_CONFIG_DIR and GEOWEBCACHE_CACHE_DIR,
* then we will check if a location was provided and then fallback on the default location.
*/
private static Resource inferConfigDirectory(ResourceStore resourceStore, String providedConfigDirectory) {
// check if a specific location was provided using a context property otherwise use the provided directory
String configDirectoryPath = findFirstDefined(
GEOWEBCACHE_CONFIG_DIR_PROPERTY, GEOWEBCACHE_CACHE_DIR_PROPERTY)
.orElse(providedConfigDirectory);
// if the configuration directory stills not defined we use the default location
if (configDirectoryPath == null) {
configDirectoryPath = DEFAULT_CONFIGURATION_DIR_NAME;
}
// instantiate a resource for the configuration directory
File configurationDirectory = new File(configDirectoryPath);
if (configurationDirectory.isAbsolute()) {
return Resources.fromPath(configurationDirectory.getAbsolutePath());
}
// configuration directory path is relative to geoserver data directory
return resourceStore.get(configDirectoryPath);
}
/**
* Returns an {@link Optional} containing the value of the first defined property,
* or an empty {@code Optional} if no property is defined in the current context.
*/
private static Optional<String> findFirstDefined(String... propertiesNames) {
for (String propertyName : propertiesNames) {
// looks the property using GeoServer extensions mechanism
String propertyValue = GeoServerExtensions.getProperty(propertyName);
if (propertyValue != null) {
// this property is defined so let's use is value
LOGGER.debug(String.format("Property '%s' is set with value '%s'.", propertyName, propertyValue));
return Optional.of(propertyValue);
}
}
// no property is defined
return Optional.empty();
}
public Resource getConfigDirectory() {
return configDirectory;
}
public String getConfigFileName() {
return configFileName;
}
@Override
public InputStream in() throws IOException {
return findOrCreateConfFile().in();
}
@Override
public OutputStream out() throws IOException {
return findOrCreateConfFile().out();
}
@Override
public void backup() throws IOException {
backUpConfig(findOrCreateConfFile());
}
@Override
public String getId() {
return configDirectory.path();
}
@Override
public void setTemplate(final String templateLocation) {
this.templateLocation = templateLocation;
}
private Resource findConfigFile() throws IOException {
return configDirectory.get(configFileName);
}
public String getLocation() throws IOException {
return findConfigFile().path();
}
private Resource findOrCreateConfFile() throws IOException {
Resource xmlFile = findConfigFile();
if (Resources.exists(xmlFile)) {
LOGGER.info("Found configuration file in " + configDirectory.path());
} else if (templateLocation != null) {
LOGGER.warn("Found no configuration file in config directory, will create one at '"
+ xmlFile.path() + "' from template "
+ getClass().getResource(templateLocation).toExternalForm());
// grab template from classpath
try {
IOUtils.copy(getClass().getResourceAsStream(templateLocation),
xmlFile.out());
} catch (IOException e) {
throw new IOException("Error copying template config to "
+ xmlFile.path(), e);
}
}
return xmlFile;
}
private void backUpConfig(final Resource xmlFile) throws IOException {
String timeStamp = new SimpleDateFormat("yyyy-MM-dd'T'HHmmss").format(new Date());
String backUpFileName = "geowebcache_" + timeStamp + ".bak";
Resource parentFile = xmlFile.parent();
LOGGER.debug("Backing up config file " + xmlFile.name() + " to " + backUpFileName);
List<Resource> previousBackUps = Resources.list(parentFile, new Filter<Resource>() {
public boolean accept(Resource res) {
if (configFileName.equals(res.name())) {
return false;
}
if (res.name().startsWith(configFileName) && res.name().endsWith(".bak")) {
return true;
}
return false;
}
});
final int maxBackups = 10;
if (previousBackUps.size() > maxBackups) {
Collections.sort(previousBackUps, new Comparator<Resource>() {
@Override
public int compare(Resource o1, Resource o2) {
return (int) (o1.lastmodified() - o2.lastmodified());
}
});
Resource oldest = previousBackUps.get(0);
LOGGER.debug("Deleting oldest config backup " + oldest + " to keep a maximum of "
+ maxBackups + " backups.");
oldest.delete();
}
Resource backUpFile = parentFile.get(backUpFileName);
IOUtils.copy(xmlFile.in(), backUpFile.out());
LOGGER.debug("Config backup done");
}
@Override
public boolean hasInput() {
try {
return Resources.exists(findOrCreateConfFile());
} catch (IOException e) {
return false;
}
}
@Override
public boolean hasOutput() {
return true;
}
}