/* * 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; } } }