/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.xml.merger.execution;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import de.rcenvironment.components.xml.merger.common.XmlMergerComponentConstants;
import de.rcenvironment.core.component.api.ComponentConstants;
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.model.spi.DefaultComponent;
import de.rcenvironment.core.component.xml.XMLComponentConstants;
import de.rcenvironment.core.component.xml.XmlComponentHistoryDataItem;
import de.rcenvironment.core.component.xml.api.EndpointXMLService;
import de.rcenvironment.core.datamodel.api.DataTypeException;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.types.api.FileReferenceTD;
import de.rcenvironment.core.utils.common.LogUtils;
import de.rcenvironment.core.utils.common.TempFileService;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
import de.rcenvironment.core.utils.common.xml.XMLException;
import de.rcenvironment.core.utils.common.xml.api.XMLMapperService;
import de.rcenvironment.core.utils.common.xml.api.XMLSupportService;
import de.rcenvironment.toolkit.utils.text.AbstractTextLinesReceiver;
/**
* Implementing class for XMLMerger functionality with file support.
*
* @author Markus Kunde
* @author Miriam Lenk#
* @author Jan FLink
* @author Brigitte Boden
*/
public class XmlMergerComponent extends DefaultComponent {
/**
* Implementation of TextLinesReceiver for XMLMerger.
*
* @author Brigitte Boden
*/
private final class XMLMergerTextLinesReceiver extends AbstractTextLinesReceiver {
@Override
public void addLine(String line) {
componentContext.getLog().componentInfo(line);
}
}
private static final String FAILED_TO_DELETE_TEMP_FILE = "Failed to delete temp file: ";
private static final Log LOG = LogFactory.getLog(XmlMergerComponent.class);
private ComponentContext componentContext;
private ComponentDataManagementService dataManagementService;
private EndpointXMLService endpointXmlUtils;
private XMLMapperService xmlMapper;
private XmlComponentHistoryDataItem historyDataItem = null;
private XMLSupportService xmlSupport;
private File tempMainFile = null;
private File tempIntegratingFile = null;
private File resultFile = null;
private File xsltFile = null;
@Override
public void setComponentContext(ComponentContext componentContext) {
this.componentContext = componentContext;
}
@Override
public void start() throws ComponentException {
dataManagementService = componentContext.getService(ComponentDataManagementService.class);
xmlSupport = componentContext.getService(XMLSupportService.class);
xmlMapper = componentContext.getService(XMLMapperService.class);
endpointXmlUtils = componentContext.getService(EndpointXMLService.class);
}
@Override
public void processInputs() throws ComponentException {
initializeNewHistoryDataItem();
TempFileService tempFileService = TempFileServiceAccess.getInstance();
String mappingContent;
String mappingType;
if (componentContext.getConfigurationValue(XmlMergerComponentConstants.MAPPINGFILE_DEPLOYMENT_CONFIGNAME).equals(
XmlMergerComponentConstants.MAPPINGFILE_DEPLOYMENT_LOADED)) {
mappingContent = componentContext.getConfigurationValue(XmlMergerComponentConstants.XMLCONTENT_CONFIGNAME);
mappingType = componentContext.getConfigurationValue(XmlMergerComponentConstants.MAPPINGTYPE_CONFIGNAME);
// If no mapping file has been loaded in the XML Merger, content and type are null at this point.
if (mappingContent == null || mappingContent.isEmpty() || mappingType == null) {
throw new ComponentException("No mapping file and/or no mapping type defined for XML Merger.");
}
} else {
FileReferenceTD mappingFile = (FileReferenceTD) componentContext.readInput(XmlMergerComponentConstants.INPUT_NAME_MAPPING_FILE);
try {
File tempmappingFile = tempFileService.createTempFileFromPattern("XMLMappingFile*");
dataManagementService.copyReferenceToLocalFile(mappingFile.getFileReference(), tempmappingFile,
componentContext.getDefaultStorageNodeId());
mappingContent = FileUtils.readFileToString(tempmappingFile);
if (mappingFile.getFileName().endsWith(XmlMergerComponentConstants.XMLFILEEND)) {
mappingType = XmlMergerComponentConstants.MAPPINGTYPE_CLASSIC;
} else {
mappingType = XmlMergerComponentConstants.MAPPINGTYPE_XSLT;
}
if (mappingContent == null || mappingContent.isEmpty() || mappingType == null) {
throw new ComponentException("No mapping file and/or no mapping type defined for XML Merger.");
}
} catch (IOException e) {
throw new ComponentException("Mapping file from input could not be read.");
}
}
FileReferenceTD mainXML = (FileReferenceTD) componentContext.readInput(XmlMergerComponentConstants.ENDPOINT_NAME_XML);
FileReferenceTD xmlToIntegrate = (FileReferenceTD) componentContext
.readInput(XmlMergerComponentConstants.INPUT_NAME_XML_TO_INTEGRATE);
tempMainFile = null;
tempIntegratingFile = null;
resultFile = null;
xsltFile = null;
try {
tempMainFile = tempFileService.createTempFileFromPattern("XMLMerger-*.xml");
tempIntegratingFile = tempFileService.createTempFileFromPattern("XMLMerger-to-integrate-*.xml");
dataManagementService.copyReferenceToLocalFile(mainXML.getFileReference(), tempMainFile,
componentContext.getDefaultStorageNodeId());
dataManagementService.copyReferenceToLocalFile(xmlToIntegrate.getFileReference(), tempIntegratingFile,
componentContext.getDefaultStorageNodeId());
} catch (IOException e) {
throw new ComponentException("Failed to write XML file into a temporary file "
+ "(that is required for XML Merger)", e);
}
Map<String, TypedDatum> variableInputs = new HashMap<>();
for (String inputName : componentContext.getInputsWithDatum()) {
if (componentContext.isDynamicInput(inputName) && !inputName.equals(XmlMergerComponentConstants.INPUT_NAME_MAPPING_FILE)) {
variableInputs.put(inputName, componentContext.readInput(inputName));
}
}
if (!variableInputs.isEmpty()) {
try {
endpointXmlUtils.updateXMLWithInputs(tempMainFile, variableInputs, componentContext);
} catch (DataTypeException e) {
throw new ComponentException("Failed to add dynamic input values to the XML file", e);
}
try {
String xmlVariableIn = dataManagementService.createTaggedReferenceFromLocalFile(componentContext, tempMainFile,
XMLComponentConstants.FILENAME);
if (historyDataItem != null && !variableInputs.isEmpty()) {
historyDataItem.setXmlWithVariablesFileReference(xmlVariableIn);
}
} catch (IOException e) {
String errorMessage = "Failed to store XML file with dynamic input values into the data management"
+ "; it will not be available in the workflow data browser";
String errorId = LogUtils.logExceptionWithStacktraceAndAssignUniqueMarker(
LogFactory.getLog(XmlMergerComponent.class), errorMessage, e);
componentContext.getLog().componentError(errorMessage, e, errorId);
}
}
if (mappingType.equals(XmlMergerComponentConstants.MAPPINGTYPE_XSLT)) {
componentContext.getLog().componentInfo("XSL transformation is applied");
try {
xsltFile = tempFileService.createTempFileFromPattern("xsltMapping*.xsl");
resultFile = tempFileService.createTempFileFromPattern("resultXML*.xml");
final String tempFilePath = tempIntegratingFile.getCanonicalPath().replaceAll("\\\\", "/");
mappingContent = mappingContent.replaceAll(XmlMergerComponentConstants.INTEGRATING_INPUT_PLACEHOLDER, tempFilePath);
FileUtils.writeStringToFile(xsltFile, mappingContent);
} catch (IOException e) {
throw new ComponentException("Failed to write XSLT mapping file into a temporary file "
+ "(that is required for XML Merger)", e);
}
try {
xmlMapper.transformXMLFileWithXSLT(tempMainFile, resultFile, xsltFile, new XMLMergerTextLinesReceiver());
} catch (XMLException e) {
throw new ComponentException("XSL transformation failed", e);
}
componentContext.getLog().componentInfo("XSL transformation successful");
} else if (mappingType.equals(XmlMergerComponentConstants.MAPPINGTYPE_CLASSIC)) {
componentContext.getLog().componentInfo("XML mapping is applied");
resultFile = map(tempMainFile, tempIntegratingFile, mappingContent);
if (tempMainFile == null || tempIntegratingFile == null || resultFile == null) {
throw new ComponentException("Something does not perform correct in XML Merger component during classic mapping."
+ " All files are not filled properly.");
}
componentContext.getLog().componentInfo("XML mapping successful");
}
try {
endpointXmlUtils.updateOutputsFromXML(resultFile, componentContext);
} catch (DataTypeException e) {
throw new ComponentException("Failed to extract dynamic output values from the merged XML file", e);
}
final FileReferenceTD fileReference;
try {
fileReference = dataManagementService.createFileReferenceTDFromLocalFile(componentContext, resultFile,
componentContext.getInstanceName() + XMLComponentConstants.XML_APPENDIX_FILENAME);
} catch (IOException e) {
throw new ComponentException("Failed to store merged XML file into the data management - "
+ "if it is not stored in the data management, it can not be sent as output value", e);
}
componentContext.writeOutput(XmlMergerComponentConstants.ENDPOINT_NAME_XML, fileReference);
storeHistoryDataItem();
deleteTempFiles();
}
@Override
public void completeStartOrProcessInputsAfterFailure() throws ComponentException {
storeHistoryDataItem();
deleteTempFiles();
}
private void deleteTempFiles() {
TempFileService tempFileService = TempFileServiceAccess.getInstance();
try {
if (tempMainFile != null) {
tempFileService.disposeManagedTempDirOrFile(tempMainFile);
}
} catch (IOException e) {
LOG.error(FAILED_TO_DELETE_TEMP_FILE + tempMainFile.getAbsolutePath(), e);
}
try {
if (tempIntegratingFile != null) {
tempFileService.disposeManagedTempDirOrFile(tempIntegratingFile);
}
} catch (IOException e) {
LOG.error(FAILED_TO_DELETE_TEMP_FILE + tempIntegratingFile.getAbsolutePath(), e);
}
try {
if (resultFile != null) {
tempFileService.disposeManagedTempDirOrFile(resultFile);
}
} catch (IOException e) {
LOG.error(FAILED_TO_DELETE_TEMP_FILE + resultFile.getAbsolutePath(), e);
}
try {
if (xsltFile != null) {
tempFileService.disposeManagedTempDirOrFile(xsltFile);
}
} catch (IOException e) {
LOG.error(FAILED_TO_DELETE_TEMP_FILE + xsltFile.getAbsolutePath(), e);
}
}
private void initializeNewHistoryDataItem() {
if (Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) {
historyDataItem = new XmlComponentHistoryDataItem(XmlMergerComponentConstants.COMPONENT_ID);
}
}
private void storeHistoryDataItem() {
if (Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) {
componentContext.writeFinalHistoryDataItem(historyDataItem);
}
}
/**
* Mapping algorithm.
*
* @param main The original XML data set
* @param integrating The second data set to integrate
* @param mappingRules TODO
* @return The combined data set
* @throws ComponentException Thrown if XML mapping fails.
*/
private File map(final File main, final File integrating, String mappingRules) throws ComponentException {
if ((main != null) && (integrating != null)) {
if (mappingRules == null || mappingRules.equals("null")) {
throw new ComponentException("Failed to perform mapping as no mapping rules are given. Check the mapping file configured");
}
try {
final Document mappingDoc = xmlSupport.readXMLFromString(mappingRules);
resultFile = TempFileServiceAccess.getInstance().createTempFileFromPattern("xml*.xml");
FileUtils.copyFile(main, resultFile);
xmlMapper.transformXMLFileWithXMLMappingInformation(integrating, resultFile, mappingDoc);
return resultFile;
} catch (XPathExpressionException | XMLException | IOException e) {
throw new ComponentException("Failed to perform XML mapping", e);
}
} else {
return null;
}
}
}