package org.gbif.ipt.action.manage;
import com.google.common.annotations.VisibleForTesting;
import org.gbif.ipt.action.POSTAction;
import org.gbif.ipt.config.AppConfig;
import org.gbif.ipt.config.Constants;
import org.gbif.ipt.config.DataDir;
import org.gbif.ipt.model.Organisation;
import org.gbif.ipt.model.Resource;
import org.gbif.ipt.service.AlreadyExistingException;
import org.gbif.ipt.service.DeletionNotAllowedException;
import org.gbif.ipt.service.ImportException;
import org.gbif.ipt.service.InvalidFilenameException;
import org.gbif.ipt.service.admin.RegistrationManager;
import org.gbif.ipt.service.admin.VocabulariesManager;
import org.gbif.ipt.service.manage.ResourceManager;
import org.gbif.ipt.struts2.SimpleTextProvider;
import org.gbif.ipt.utils.MapUtils;
import org.gbif.ipt.validation.ResourceValidator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
public class CreateResourceAction extends POSTAction {
// logging
private static final Logger LOG = Logger.getLogger(CreateResourceAction.class);
private ResourceManager resourceManager;
private DataDir dataDir;
private File file;
private String fileContentType;
private String fileFileName;
private String shortname;
private String resourceType;
private Map<String, String> types;
private List<Organisation> organisations;
private final VocabulariesManager vocabManager;
private final ResourceValidator validator = new ResourceValidator();
@Inject
public CreateResourceAction(SimpleTextProvider textProvider, AppConfig cfg, RegistrationManager registrationManager,
ResourceManager resourceManager, DataDir dataDir, VocabulariesManager vocabManager) {
super(textProvider, cfg, registrationManager);
this.resourceManager = resourceManager;
this.dataDir = dataDir;
this.vocabManager = vocabManager;
}
public String getShortname() {
return shortname;
}
@Override
public void prepare() {
super.prepare();
// load organisations able to host
organisations = registrationManager.list();
}
@Override
public String save() throws IOException {
// validation already checked that shortname was not null and valid
if (resourceManager.get(shortname) != null) {
addFieldError("resource.shortname", getText("validation.resource.shortname.exists", new String[] {shortname}));
return INPUT;
}
Date start = new Date();
// 10 seconds subtracted to accommodate differences in file system date resolution (e.g. Mac HFS has 1s resolution)
long startTimeInMs = start.getTime() - 10000;
try {
File tmpFile = uploadToTmp();
if (tmpFile == null) {
resourceManager.create(shortname, resourceType, getCurrentUser());
} else {
resourceManager.create(shortname, resourceType, tmpFile, getCurrentUser(), this);
}
} catch (AlreadyExistingException e) {
addFieldError("resource.shortname", getText("validation.resource.shortname.exists", new String[] {shortname}));
return INPUT;
} catch (ImportException e) {
LOG.error("Error importing the dwc archive: " + e.getMessage(), e);
addActionError(getText("validation.resource.import.exception"));
// remove resource and its resource folder from data directory
cleanupResourceFolder(shortname, startTimeInMs);
return INPUT;
} catch (InvalidFilenameException e) {
addActionError(getText("manage.source.invalidFileName"));
return INPUT;
}
return SUCCESS;
}
/**
* Delete resource and recursively delete its resource folder from data directory.
* As a safeguard, the resource folder must have been created after the provided start time in milliseconds.
*
* @param shortname shortname of resource to delete
* @param startTimeInMs date when resource creation started in milliseconds
*/
@VisibleForTesting
protected void cleanupResourceFolder(String shortname, long startTimeInMs) {
Preconditions.checkNotNull(shortname);
Preconditions.checkNotNull(startTimeInMs);
Resource resource = resourceManager.get(shortname);
File directory = new File(dataDir.dataFile(DataDir.RESOURCES_DIR), shortname);
if (resource != null && directory.exists() && directory.isDirectory() && directory.lastModified() > startTimeInMs) {
LOG.info("Deleting resource and its folder from data directory: " + directory);
try {
resourceManager.delete(resource, true);
} catch (IOException e) {
LOG.error("Deleting resource failed: " + e.getMessage(), e);
} catch (DeletionNotAllowedException e) {
LOG.error("Deleting resource not allowed", e);
}
}
}
public void setFile(File file) {
this.file = file;
}
public void setFileContentType(String fileContentType) {
this.fileContentType = fileContentType;
}
public void setFileFileName(String fileFileName) {
this.fileFileName = fileFileName;
}
public void setShortname(String shortname) {
this.shortname = shortname;
}
/**
* Upload file to temp file.
*
* @return uploaded file
*
* @throws ImportException if file type was invalid
*/
private File uploadToTmp() throws ImportException {
if (fileFileName == null) {
return null;
}
// the file to upload to
File tmpFile = dataDir.tmpFile(shortname, fileFileName);
LOG.debug("Uploading dwc archive file for new resource " + shortname + " to " + tmpFile.getAbsolutePath());
// retrieve the file data
InputStream input = null;
OutputStream output = null;
try {
input = new FileInputStream(file);
// write the file to the file specified
output = new FileOutputStream(tmpFile);
IOUtils.copy(input, output);
output.flush();
LOG.debug("Uploaded file " + fileFileName + " with content-type " + fileContentType);
} catch (IOException e) {
LOG.error(e);
throw new ImportException("Failed to upload file to tmp file", e);
} finally {
if (output != null) {
IOUtils.closeQuietly(output);
}
if (input != null) {
IOUtils.closeQuietly(input);
}
}
return tmpFile;
}
@Override
public void validate() {
if (isHttpPost()) {
validator.validateShortname(this, shortname);
}
}
/**
* Resource (core) type.
*
* @return resource core type
*/
@Nullable
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
/**
* Get map of resource types to populate resource type selection.
* Dataset core type list, derived from XML vocabulary, and displayed in drop-down on Basic Metadata page
*
* @return map of resource types
*/
public Map<String, String> getTypes() {
types = new LinkedHashMap<String, String>();
types.put("", getText("manage.resource.create.coreType.selection"));
types.putAll(vocabManager.getI18nVocab(Constants.VOCAB_URI_DATASET_TYPE, getLocaleLanguage(), false));
types = MapUtils.getMapWithLowercaseKeys(types);
return types;
}
/**
* @return list of organisations that can host
*/
public List<Organisation> getOrganisations() {
return organisations;
}
/**
* @return DataDir
*/
@VisibleForTesting
public DataDir getDataDir() {
return dataDir;
}
}