/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.outputwriter.execution;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
import de.rcenvironment.components.outputwriter.common.OutputLocation;
import de.rcenvironment.components.outputwriter.common.OutputLocationList;
import de.rcenvironment.components.outputwriter.common.OutputWriterComponentConstants;
import de.rcenvironment.core.component.api.ComponentException;
import de.rcenvironment.core.component.datamanagement.api.ComponentDataManagementService;
import de.rcenvironment.core.component.execution.api.ComponentContext;
import de.rcenvironment.core.component.execution.api.ComponentLog;
import de.rcenvironment.core.component.model.spi.DefaultComponent;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.types.api.DirectoryReferenceTD;
import de.rcenvironment.core.datamodel.types.api.FileReferenceTD;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.TempFileService;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
/**
* The default Outputwriter backend.
*
* @author Hendrik Abbenhaus
* @author Sascha Zur
* @author Brigitte Boden
*
*/
public class OutputWriterComponent extends DefaultComponent {
private static final String BACKSLASHES = "\\";
private static final String DOT = ".";
private static final String DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss-S";
private ComponentContext componentContext;
private ComponentLog componentLog;
private ComponentDataManagementService dataManagementService;
private TempFileService tempFileService = TempFileServiceAccess.getInstance();
private String root = "";
private String wfStartTimeStamp;
private Map<String, OutputLocationWriter> inputNameToOutputLocationWriter = new HashMap<String, OutputLocationWriter>();
@Override
public void setComponentContext(ComponentContext componentContext) {
this.componentContext = componentContext;
componentLog = componentContext.getLog();
}
@Override
public void start() throws ComponentException {
dataManagementService = componentContext.getService(ComponentDataManagementService.class);
Date dt = new Date();
SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);
wfStartTimeStamp = df.format(dt);
String rootonworkflowstart = componentContext.getConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_ONWFSTART);
boolean onwfstart = Boolean.parseBoolean(rootonworkflowstart);
if (onwfstart) {
this.root = componentContext.getConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_ONWFSTART_ROOT);
} else {
this.root = componentContext.getConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_ROOT);
}
// Parse list of outputLocations and initialize corresponding objects
String jsonString = componentContext.getConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_OUTPUTLOCATIONS);
// For "old" outputWriters that only have file/directory inputs, the jsonString may not be set
if (jsonString != null && !jsonString.isEmpty()) {
ObjectMapper jsonMapper = JsonUtils.getDefaultObjectMapper();
jsonMapper.setVisibility(JsonMethod.ALL, Visibility.ANY);
try {
OutputLocationList outputList = jsonMapper.readValue(jsonString, OutputLocationList.class);
for (OutputLocation out : outputList.getOutputLocations()) {
OutputLocationWriter writer =
new OutputLocationWriter(out.getInputs(), out.getHeader(), out.getFormatString(),
out.getHandleExistingFile(), componentLog);
for (String input : out.getInputs()) {
inputNameToOutputLocationWriter.put(input, writer);
}
// Initialize the file for the outputLocation. Checks if the file already exists at component start. If yes, the
// filename is changed.
// NOTE: The handling of a file existing at workflow start (always AUTORENAME) is done here (as it is the same as for
// files and directories), whereas the handling of files in later iterations is done in the OutputLocationWriter.
String path = out.getFolderForSaving() + File.separator + out.getFilename();
path = path.substring(OutputWriterComponentConstants.ROOT_DISPLAY_NAME.length() + 1);
path = replacePlaceholder(path, "", null);
File fileToWrite = new File(root + File.separator + path);
writer.initializeFile(fileToWrite);
}
} catch (IOException e) {
throw new ComponentException("Failed to parse (internal) configuration (JSON string)", e);
}
}
}
@Override
public void processInputs() throws ComponentException {
Date dt = new Date();
SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);
String inputName = componentContext.getInputsWithDatum().iterator().next();
TypedDatum input = componentContext.readInput(inputName);
if (input.getDataType().equals(DataType.DirectoryReference) || input.getDataType().equals(DataType.FileReference)) {
processFileOrDirectory(inputName, input);
} else if (inputNameToOutputLocationWriter.get(inputName) != null) {
Map<String, TypedDatum> inputMap = new HashMap<String, TypedDatum>();
for (String name : componentContext.getInputsWithDatum()) {
inputMap.put(name, componentContext.readInput(name));
}
inputNameToOutputLocationWriter.get(inputName).writeOutput(inputMap, df.format(dt), componentContext.getExecutionCount());
} else {
componentLog.componentWarn(StringUtils.format("Received value for input '%s' that is"
+ " not associated with any target for simple data types", inputName));
}
}
private void processFileOrDirectory(String inputName, TypedDatum input) throws ComponentException {
String path = componentContext.getInputMetaDataValue(inputName, OutputWriterComponentConstants.CONFIG_KEY_FOLDERFORSAVING)
+ File.separator
+ componentContext.getInputMetaDataValue(inputName, OutputWriterComponentConstants.CONFIG_KEY_FILENAME);
path = path.substring(OutputWriterComponentConstants.ROOT_DISPLAY_NAME.length() + 1);
String origFilename = null;
if (input.getDataType().equals(DataType.DirectoryReference)) {
origFilename = ((DirectoryReferenceTD) input).getDirectoryName();
} else if (input.getDataType().equals(DataType.FileReference)) {
origFilename = ((FileReferenceTD) input).getFileName();
}
path = replacePlaceholder(path, inputName, origFilename);
File fileToWrite = new File(root + File.separator + path);
if (!fileToWrite.exists()) {
writeFile(input, root + File.separator + path, inputName);
} else {
File possibleFile = autoRename(fileToWrite);
writeFile(input, possibleFile.getAbsolutePath(), inputName);
}
if (input.getDataType().equals(DataType.DirectoryReference)) {
componentLog.componentInfo(StringUtils.format("Wrote directory '%s' of input '%s' to: %s",
((DirectoryReferenceTD) input).getDirectoryName(), inputName, fileToWrite.getAbsolutePath()));
} else if (input.getDataType().equals(DataType.FileReference)) {
componentLog.componentInfo(StringUtils.format("Wrote file '%s' of input '%s' to: %s",
((FileReferenceTD) input).getFileName(), inputName, fileToWrite.getAbsolutePath()));
}
}
protected File autoRename(File fileToWrite) {
String folderpath = fileToWrite.getParent();
String fileName = fileToWrite.getName();
String extension = "";
if (fileName.contains(DOT)) {
extension = fileName.substring(fileName.lastIndexOf(DOT));
fileName = fileName.substring(0, fileName.lastIndexOf(DOT));
}
int i = 1;
File possibleFile = new File(folderpath, fileName + " (" + i + ")" + extension);
while (possibleFile.exists()) {
possibleFile = new File(folderpath, fileName + " (" + ++i + ")" + extension);
}
componentLog.componentInfo(StringUtils.format("File '%s' already exists, "
+ "renamed to: %s", fileToWrite.getAbsolutePath(), possibleFile.getAbsolutePath()));
return possibleFile;
}
/**
* Replaces the placeholder.
*
* @param pathinput contains a input
* @param currentInputName contains a current input name
* @param filename TODO
* @return the input without placeholder
* @throws ComponentException when function was not able to replace all placeholders
*/
public String replacePlaceholder(String pathinput, String currentInputName, String filename) throws ComponentException {
String output = pathinput;
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_WORKFLOWNAME), componentContext.getWorkflowInstanceName()
.replaceAll(":", "-"));
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_INPUTNAME), currentInputName);
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_WF_START_TS), wfStartTimeStamp);
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_COMP_NAME), componentContext.getInstanceName());
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_COMP_TYPE), componentContext.getComponentName());
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_EXECUTION_COUNT),
Integer.toString(componentContext.getExecutionCount()));
// In the case of simple data inputs, there is no initial filename, so this placeholder is only replaced for files/directories.
if (filename != null) {
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_FILE_NAME), filename);
}
Date dt = new Date();
SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);
output =
output.replaceAll(escapePlaceholder(OutputWriterComponentConstants.PH_TIMESTAMP), df.format(dt));
return output;
}
private String escapePlaceholder(String placeholder) {
placeholder = placeholder.replace(OutputWriterComponentConstants.PH_PREFIX, BACKSLASHES + OutputWriterComponentConstants.PH_PREFIX);
return placeholder.replace(OutputWriterComponentConstants.PH_SUFFIX, BACKSLASHES + OutputWriterComponentConstants.PH_SUFFIX);
}
private void writeFile(TypedDatum input, String path, String inputName) throws ComponentException {
File file = new File(path);
String filename = file.getName();
file = new File(file.getAbsolutePath().replace(File.separator + filename, ""));
if (!file.isDirectory()) {
file.mkdirs();
}
final File incFileOrDir;
switch (input.getDataType()) {
case FileReference:
// Check for invalid filename
List<String> forbiddenFilenames = Arrays.asList(OutputWriterComponentConstants.PROBLEMATICFILENAMES_WIN);
if (forbiddenFilenames.contains(filename) || filename.contains("/") || filename.contains("\\")) {
throw new ComponentException(StringUtils.format("Failed to write file of input '%s' beacuse '%s' "
+ "is a forbidden filename", inputName, filename));
}
incFileOrDir = new File(path);
try {
dataManagementService.copyReferenceToLocalFile(((FileReferenceTD) input).getFileReference(),
incFileOrDir, componentContext.getDefaultStorageNodeId());
} catch (IOException e) {
throw new ComponentException(StringUtils.format("Failed to write file of input '%s' to %s",
inputName, incFileOrDir.getAbsolutePath()), e);
}
componentLog.componentInfo(StringUtils.format("Wrote file of input '%s' to: %s", inputName, incFileOrDir.getAbsolutePath()));
break;
case DirectoryReference:
incFileOrDir = new File(path);
File tempDir;
try {
tempDir = tempFileService.createManagedTempDir();
} catch (IOException e) {
throw new ComponentException("Failed to create temporary directory that is required by Output Writer", e);
}
try {
dataManagementService.copyDirectoryReferenceTDToLocalDirectory(componentContext, ((DirectoryReferenceTD) input),
tempDir);
FileUtils.moveDirectory(new File(tempDir, ((DirectoryReferenceTD) input).getDirectoryName()), incFileOrDir);
} catch (IOException e) {
throw new ComponentException(StringUtils.format("Failed to write directory of input '%s' to %s",
inputName, incFileOrDir.getAbsolutePath()), e);
} finally {
try {
tempFileService.disposeManagedTempDirOrFile(tempDir);
} catch (IOException e) {
LogFactory.getLog(getClass()).error("Failed to delete temporary directory", e);
}
}
componentLog.componentInfo(StringUtils.format("Wrote directory of input '%s' to: %s",
inputName, incFileOrDir.getAbsolutePath()));
break;
default:
break;
}
}
@Override
public void tearDown(FinalComponentState state) {
super.tearDown(state);
// Close all the output streams
for (OutputLocationWriter out : inputNameToOutputLocationWriter.values()) {
out.close();
}
}
}