/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Arne Kepp, Marius Suta, The Open Planning Project, Copyright 2008 - 2015
* @author Niels Charlier
*/
package org.geowebcache.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.storage.DefaultStorageFinder;
import org.geowebcache.util.ApplicationContextProvider;
import org.geowebcache.util.GWCVars;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/**
* Default implementation of ConfigurationResourceProvider that uses the file system.
*
*
*/
public class XMLFileResourceProvider implements ConfigurationResourceProvider {
private static Log log = LogFactory.getLog(org.geowebcache.config.XMLFileResourceProvider.class);
public static final String GWC_CONFIG_DIR_VAR = "GEOWEBCACHE_CONFIG_DIR";
/**
* Web app context, used to look up {@link XMLConfigurationProvider}s. Will be null if used the
* {@link #XMLConfiguration(File)} constructor
*/
private final WebApplicationContext context;
/**
* Location of the configuration file
*/
private final File configDirectory;
/**
* Name of the configuration file
*/
private final String configFileName;
private String templateLocation;
public XMLFileResourceProvider(final String configFileName,
final WebApplicationContext appCtx,
final String configFileDirectory,
final DefaultStorageFinder storageDirFinder) throws ConfigurationException {
if(configFileDirectory==null && storageDirFinder==null) {
throw new NullPointerException("At least one of configFileDirectory or storageDirFinder must not be null");
}
this.context = appCtx;
this.configFileName = configFileName;
if(configFileDirectory!=null) {
// Use the given path
if (new File(configFileDirectory).isAbsolute()) {
log.info("Provided configuration directory as absolute path '" + configFileDirectory + "'");
this.configDirectory = new File(configFileDirectory);
} else {
String baseDir = context.getServletContext().getRealPath("");
log.info("Provided configuration directory relative to servlet context '" + baseDir + "': "
+ configFileDirectory);
this.configDirectory = new File(baseDir, configFileDirectory);
}
} else {
// Otherwise use the storage directory
this.configDirectory = new File(storageDirFinder.getDefaultPath());
}
log.info("Will look for " + configFileName + " in '" + configDirectory + "'");
}
public XMLFileResourceProvider(final String configFileName,
final ApplicationContextProvider appCtx,
final String configFileDirectory,
final DefaultStorageFinder storageDirFinder) throws ConfigurationException {
this(configFileName, appCtx == null ? null : appCtx.getApplicationContext(), configFileDirectory,
storageDirFinder);
}
/**
* Constructor that will look for {@code geowebcache.xml} at the directory defined by
* {@code storageDirFinder}
*
* @param appCtx
* use to lookup {@link XMLConfigurationProvider} extenions, may be {@code null}
* @param defaultStorage
* @throws ConfigurationException
*/
public XMLFileResourceProvider(final String configFileName,
final ApplicationContextProvider appCtx,
final DefaultStorageFinder storageDirFinder) throws ConfigurationException {
this(configFileName, appCtx, getConfigDirVar(appCtx.getApplicationContext()), storageDirFinder);
}
/**
* Constructor that will look for {@code geowebcache.xml} at the directory defined by
* {@code storageDirFinder}
*
* @param appCtx
* use to lookup {@link XMLConfigurationProvider} extenions, may be {@code null}
* @param defaultStorage
* @throws ConfigurationException
*/
public XMLFileResourceProvider(final String configFileName,
final WebApplicationContext appCtx,
final DefaultStorageFinder storageDirFinder) throws ConfigurationException {
this(configFileName, appCtx, getConfigDirVar(appCtx), storageDirFinder);
}
@Override
public InputStream in() throws IOException {
return new FileInputStream(findOrCreateConfFile());
}
@Override
public OutputStream out() throws IOException {
return new FileOutputStream(findOrCreateConfFile());
}
@Override
public void backup() throws IOException {
backUpConfig(findOrCreateConfFile());
}
@Override
public String getId() {
return configDirectory.getAbsolutePath();
}
private static String getConfigDirVar(ApplicationContext ctxt){
return GWCVars.findEnvVar(ctxt, GWC_CONFIG_DIR_VAR);
}
@Override
public void setTemplate(final String templateLocation) {
this.templateLocation = templateLocation;
}
private File findConfigFile() throws IOException {
if (null == configDirectory) {
throw new IllegalStateException();
}
if (!configDirectory.exists() && !configDirectory.mkdirs()) {
throw new IOException(
"Configuration directory does not exist and cannot be created: '"
+ configDirectory.getAbsolutePath() + "'");
}
if (!configDirectory.canWrite()) {
throw new IOException("Configuration directory is not writable: '"
+ configDirectory.getAbsolutePath() + "'");
}
File xmlFile = new File(configDirectory, configFileName);
return xmlFile;
}
public String getLocation() throws IOException {
File f = findConfigFile();
try {
return f.getCanonicalPath();
} catch (IOException ex) {
log.error("Could not canonize config path", ex);
return f.getPath();
}
}
private File findOrCreateConfFile() throws IOException {
File xmlFile = findConfigFile();
if (xmlFile.exists()) {
log.info("Found configuration file in " + configDirectory.getAbsolutePath());
} else if (templateLocation != null) {
log.warn("Found no configuration file in config directory, will create one at '"
+ xmlFile.getAbsolutePath() + "' from template "
+ getClass().getResource(templateLocation).toExternalForm());
// grab template from classpath
try {
InputStream templateStream = getClass().getResourceAsStream(templateLocation);
try {
OutputStream output = new FileOutputStream(xmlFile);
try {
IOUtils.copy(templateStream, output);
} finally {
output.flush();
output.close();
}
} finally {
templateStream.close();
}
} catch (IOException e) {
throw new IOException("Error copying template config to "
+ xmlFile.getAbsolutePath(), e);
}
}
return xmlFile;
}
private void backUpConfig(final File xmlFile) throws IOException {
String timeStamp = new SimpleDateFormat("yyyy-MM-dd'T'HHmmss").format(new Date());
String backUpFileName = "geowebcache_" + timeStamp + ".bak";
File parentFile = xmlFile.getParentFile();
log.debug("Backing up config file " + xmlFile.getName() + " to " + backUpFileName);
String[] previousBackUps = parentFile.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (configFileName.equals(name)) {
return false;
}
if (name.startsWith(configFileName) && name.endsWith(".bak")) {
return true;
}
return false;
}
});
final int maxBackups = 10;
if (previousBackUps.length > maxBackups) {
Arrays.sort(previousBackUps);
String oldest = previousBackUps[0];
log.debug("Deleting oldest config backup " + oldest + " to keep a maximum of "
+ maxBackups + " backups.");
new File(parentFile, oldest).delete();
}
File backUpFile = new File(parentFile, backUpFileName);
FileUtils.copyFile(xmlFile, backUpFile);
log.debug("Config backup done");
}
@Override
public boolean hasInput() {
try {
return findOrCreateConfFile().exists();
} catch (IOException e) {
return false;
}
}
@Override
public boolean hasOutput() {
return true;
}
}