package org.gbif.ipt.config;
import org.gbif.ipt.model.FileSource;
import org.gbif.ipt.model.Resource;
import org.gbif.ipt.service.InvalidConfigException;
import org.gbif.ipt.service.InvalidConfigException.TYPE;
import org.gbif.ipt.utils.InputStreamUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Random;
import javax.validation.constraints.NotNull;
import com.google.inject.Singleton;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
/**
* A very simple utility class to encapsulate the basic layout of the data directory and to configure & persist the
* path for that directory and make it available to the entire application.
*/
@Singleton
public class DataDir {
public static final String LOGGING_DIR = "logs";
public static final String CONFIG_DIR = "config";
public static final String RESOURCES_DIR = "resources";
public static final String TMP_DIR = "tmp";
public static final String EML_XML_FILENAME = "eml.xml";
public static final String DWCA_FILENAME = "dwca.zip";
public static final String PUBLICATION_LOG_FILENAME = "publication.log";
private static final Random RANDOM = new Random();
private static Logger log = Logger.getLogger(DataDir.class);
protected File dataDir;
private File dataDirSettingFile;
private InputStreamUtils streamUtils = new InputStreamUtils();
private DataDir() {
}
/**
* Build and configure new IPT Data Directory instance from location settings file.
*
* @param dataDirSettingFile location settings file specifying location of existing IPT data directory
*
* @return IPT Data Directory instance
*/
public static DataDir buildFromLocationFile(File dataDirSettingFile) {
DataDir dd = new DataDir();
dd.dataDirSettingFile = dataDirSettingFile;
if (dataDirSettingFile != null && dataDirSettingFile.exists()) {
String dataDirPath = null;
try {
dataDirPath = StringUtils.trimToNull(FileUtils.readFileToString(dataDirSettingFile, "UTF-8"));
if (dataDirPath != null) {
log.info("IPT Data Directory configured at " + dataDirPath);
dd.dataDir = new File(dataDirPath);
}
} catch (IOException e) {
log.error(
"Failed to read the IPT Data Directory location settings file in WEB-INF at " + dataDirSettingFile.getAbsolutePath(), e);
}
} else {
log.warn("IPT Data Directory location settings file in WEB-INF not found. Continue without data directory.");
}
return dd;
}
/**
* Build and configure new IPT Data Directory instance from specified path.
*
* @param dataDirPath location of existing IPT Data Directory
*
* @return IPT Data Directory instance
*/
public static DataDir buildFromString(String dataDirPath) {
DataDir dd = new DataDir();
if (dataDirPath != null) {
log.info("IPT Data Directory configured at " + dataDirPath);
dd.dataDir = new File(dataDirPath);
}
return dd;
}
private void assureDirExists(File f) {
if (f != null && !f.exists()) {
f.mkdirs();
}
}
private void assureParentExists(File f) {
if (f != null && !f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
}
protected void clearTmp() throws IOException {
File tmpDir = tmpFile("");
FileUtils.forceMkdir(tmpDir);
FileUtils.cleanDirectory(tmpDir);
log.debug("Cleared temporary folder");
}
/**
* Constructs an absolute path to a file within the config folder of the data dir.
*
* @param path the relative path within the config folder
*/
public File configFile(String path) {
return dataFile(CONFIG_DIR + "/" + path);
}
private void createDefaultDir() throws IOException {
// create config, resources
File configDir = dataFile(CONFIG_DIR);
File resourcesDir = dataFile(RESOURCES_DIR);
File loggingDir = dataFile(LOGGING_DIR);
FileUtils.forceMkdir(configDir);
FileUtils.forceMkdir(resourcesDir);
FileUtils.forceMkdir(loggingDir);
// copy default config files
InputStream input = streamUtils.classpathStream("configDefault/ipt.properties");
if (input == null) {
throw new InvalidConfigException(TYPE.CONFIG_WRITE,
"Cannot read required classpath resources to create new data dir!");
}
org.gbif.ipt.utils.FileUtils.copyStreamToFile(input, configFile(AppConfig.DATADIR_PROPFILE));
input = streamUtils.classpathStream("configDefault/about.ftl");
if (input == null) {
throw new InvalidConfigException(TYPE.CONFIG_WRITE,
"Cannot read required classpath resources to create new data dir!");
}
org.gbif.ipt.utils.FileUtils.copyStreamToFile(input, configFile("about.ftl"));
log.info("Creating new default data dir");
}
/**
* Basic method to convert a relative path within the data dir to an absolute path on the filesystem.
*
* @param path the relative path within the data dir
*/
public File dataFile(String path) {
if (dataDir == null) {
throw new IllegalStateException("No data dir has been configured yet");
}
File f = new File(dataDir, path);
assureParentExists(f);
return f;
}
/**
* @return true if a working data directory is configured
*/
public boolean isConfigured() {
return dataDir != null && dataDir.exists();
}
/**
* Constructs an absolute path to the logs folder of the data dir.
*/
public File loggingDir() {
return dataFile(LOGGING_DIR);
}
/**
* Constructs an absolute path to a file within the logs folder of the data dir.
*
* @param path the relative path within the logs folder
*/
public File loggingFile(String path) {
return dataFile(LOGGING_DIR + "/" + path);
}
private void persistLocation() throws IOException {
// persist location in WEB-INF
FileUtils.writeStringToFile(dataDirSettingFile, dataDir.getAbsolutePath());
log.info("IPT DataDir location file in /WEB-INF changed to " + dataDir.getAbsolutePath());
}
/**
* Retrieves published DwC-A file for a specific version of a resource.
*
* @param resourceName resource short name
* @param version version
*
* @return DwC-A file having specific version
*/
public File resourceDwcaFile(@NotNull String resourceName, @NotNull BigDecimal version) {
String fn = "dwca-" + version.toPlainString() + ".zip";
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + fn);
}
/**
* Retrieves DwC-A file for a resource.
*
* @param resourceName resource short name
*
* @return DwC-A file having specific version
*/
public File resourceDwcaFile(@NotNull String resourceName) {
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + DWCA_FILENAME);
}
/**
* Retrieves published EML file for a specific version of a resource.
*
* @param resourceName resource short name
* @param version version
*
* @return EML file having specific version
*/
public File resourceEmlFile(@NotNull String resourceName, @NotNull BigDecimal version) {
String fn = "eml-" + version.toPlainString() + ".xml";
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + fn);
}
/**
* Retrieves EML file for a resource.
*
* @param resourceName resource short name
*
* @return interim EML file for resource
*/
public File resourceEmlFile(@NotNull String resourceName) {
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + EML_XML_FILENAME);
}
public File resourceFile(Resource resource, String path) {
if (resource == null) {
return null;
}
return resourceFile(resource.getShortname(), path);
}
/**
* Constructs an absolute path to a file within a resource folder inside the data dir
*
* @param path the relative path within the individual resource folder
*/
public File resourceFile(String resourceName, String path) {
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + path);
}
/**
* @param suffix the logo file suffix, indicating the format. E.g. jpeg or gif
*/
public File resourceLogoFile(String resourceName, String suffix) {
if (suffix == null) {
suffix = "jpeg";
}
suffix = suffix.toLowerCase();
return dataFile(RESOURCES_DIR + "/" + resourceName + "/logo." + suffix);
}
public File resourcePublicationLogFile(String resourceName) {
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + PUBLICATION_LOG_FILENAME);
}
/**
* Retrieves published RTF file for a specific version of a resource.
*
* @param resourceName resource short name
* @param version version
*
* @return RTF file having specific version, defaulting to latest published version if no version specified
*/
public File resourceRtfFile(@NotNull String resourceName, @NotNull BigDecimal version) {
String fn = resourceName + "-" + version.toPlainString() + ".rtf";
return dataFile(RESOURCES_DIR + "/" + resourceName + "/" + fn);
}
/**
* Sets the path to the data directory for the entire application and persists it in the /WEB-INF folder. This method
* does not reload any configuration though - so normally setting the dataDir should be done through the
* ConfigManager
* which calls this method but also reloads all user configurations.
*
* @return true if a new data dir was created, false when an existing was read
*/
public boolean setDataDir(File dataDir) throws InvalidConfigException {
if (dataDir == null) {
throw new NullPointerException("DataDir file required");
} else {
this.dataDir = dataDir;
File configDir = configFile("");
if (dataDir.exists() && (!dataDir.isDirectory() || dataDir.list().length > 0)) {
// EXISTING file or directory with content: make sure its an IPT datadir - otherwise break
if (dataDir.isDirectory()) {
// check if this directory contains a config folder - if not copy empty default dir from classpath
if (!configDir.exists() || !configDir.isDirectory()) {
this.dataDir = null;
throw new InvalidConfigException(TYPE.INVALID_DATA_DIR,
"DataDir " + dataDir.getAbsolutePath() + " exists already and is no IPT data dir.");
}
log.info("Reusing existing data dir.");
// persist location in WEB-INF
try {
persistLocation();
} catch (IOException e) {
log.error("Cant persist datadir location in WEBINF webapp folder", e);
}
return false;
} else {
this.dataDir = null;
throw new InvalidConfigException(TYPE.INVALID_DATA_DIR,
"DataDir " + dataDir.getAbsolutePath() + " is not a directory");
}
} else {
// NEW datadir
try {
// create new main data dir. Populate later
FileUtils.forceMkdir(dataDir);
// test if we can write to the directory
File testFile = new File(dataDir, "test.tmp");
FileUtils.touch(testFile);
// remove test file
testFile.delete();
// create new default data dir
createDefaultDir();
// all works fine - persist location in WEB-INF
persistLocation();
return true;
} catch (IOException e) {
log.error("New DataDir " + dataDir.getAbsolutePath() + " not writable", e);
this.dataDir = null;
throw new InvalidConfigException(InvalidConfigException.TYPE.NON_WRITABLE_DATA_DIR,
"DataDir " + dataDir.getAbsolutePath() + " is not writable");
}
}
}
}
public File sourceFile(Resource resource, FileSource source) {
if (resource == null) {
return null;
}
return resourceFile(resource.getShortname(), "sources/" + source.getName() + source.getPreferredFileSuffix());
}
public File sourceLogFile(String resourceName, String sourceName) {
return dataFile(RESOURCES_DIR + "/" + resourceName + "/sources/" + sourceName + ".log");
}
/**
* Return a temporary directory with randomly-generated number added to name to uniquely identifier it.
*
* @return temporary directory
*/
public File tmpDir() {
String random = String.valueOf(RANDOM.nextLong());
File dir = tmpFile("dir" + random);
assureDirExists(dir);
return dir;
}
/**
* Construct the absolute path for a given relative path within the temporary folder. This method doesn't generate a
* unique filename - it only uses the path provided.
*
* @param path relative file path within temporary folder
*
* @return temporary file within relative file path
*/
public File tmpFile(String path) {
return dataFile(TMP_DIR + "/" + path);
}
/**
* Generates an unused temporary filename, using an auto-generated number in the name. The File handle gets returned.
*
* @param prefix file name prefix, e.g. "dwca"
* @param suffix file name suffix, e.g. ".zip"
*
* @return temporary File handle with unique filename
*/
public File tmpFile(String prefix, String suffix) {
String random = String.valueOf(RANDOM.nextInt());
return tmpFile(prefix + random + suffix);
}
}