package org.opentosca.csarrepo.service;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import net.lingala.zip4j.exception.ZipException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opentosca.csarrepo.exception.PersistenceException;
import org.opentosca.csarrepo.filesystem.FileSystem;
import org.opentosca.csarrepo.model.Csar;
import org.opentosca.csarrepo.model.CsarFile;
import org.opentosca.csarrepo.model.HashedFile;
import org.opentosca.csarrepo.model.Plan;
import org.opentosca.csarrepo.model.repository.CsarFileRepository;
import org.opentosca.csarrepo.model.repository.CsarPlanRepository;
import org.opentosca.csarrepo.model.repository.CsarRepository;
import org.opentosca.csarrepo.model.repository.FileSystemRepository;
import org.opentosca.csarrepo.util.Extractor;
import org.opentosca.csarrepo.util.StringUtils;
import org.opentosca.csarrepo.util.ZipUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @author eiselems (marcus.eisele@gmail.com), Dennis Przytarski
*
*/
public class UploadCsarFileService extends AbstractService {
private static final String CSAR_REPOSITORY_FILENAME = "CSAR-REPOSITORY.txt";
private static final String ENTRY_DEFINITION_PATTERN = "Entry-Definitions: ([\\S]+)\\n";
private static final String TOSCA_METADATA_FILEPATH = "TOSCA-Metadata/TOSCA.meta";
private static final Logger LOGGER = LogManager.getLogger(UploadCsarFileService.class);
private static final String XPATH_PLANS_FROM_SERVICETEMPLATE = "//*[local-name()='Plan']";
private static final String XPATH_PLANMODELREFERENCE_REFERENCE = "//*[local-name()='PlanModelReference']/@*[name()='reference']";
private CsarFile csarFile;
// TODO: check if this is the newest one, or remove namespace checking by
// replacing it with "*"
private static final String SERVICETEMPLATE_NS = "http://docs.oasis-open.org/tosca/ns/2011/12";
private static final String SERVICETEMPLATE_LOCALNAME = "ServiceTemplate";
private static final String BUILDPLAN_TYPE_TOSCA = "http://docs.oasis-open.org/tosca/ns/2011/12/PlanTypes/BuildPlan";
/**
* @param userId
* @param file
* @throws IOException
*/
public UploadCsarFileService(long userId, long csarId, InputStream inputStream, String name) {
super(userId);
if (!checkExtension(name, "csar")) {
this.addError(String.format("Uploaded file %s does not contain required extension", name));
return;
}
storeFile(csarId, inputStream, name);
}
/**
* Checks, if the name contains the given extension.
*
* @param name
* @param extension
* @return true, if given name contains given extension
*/
private boolean checkExtension(String name, String extension) {
int index = name.lastIndexOf('.');
return 0 < index && name.substring(index + 1).equals(extension);
}
/**
* Moves the uploaded file to the filesystem and creates a csar file.
*
* @param csarId
* @param inputStream
* @param name
*/
private void storeFile(long csarId, InputStream inputStream, String name) {
CsarRepository csarRepository = new CsarRepository();
CsarFileRepository csarFileRepository = new CsarFileRepository();
FileSystemRepository fileSystemRepository = new FileSystemRepository();
try {
Csar csar = csarRepository.getbyId(csarId);
if (null == csar) {
String errorMsg = String.format("CSAR with ID: %d could not be found", csarId);
this.addError(errorMsg);
LOGGER.error(errorMsg);
return;
}
FileSystem fileSystem = new FileSystem();
File temporaryFile = fileSystem.saveTempFile(inputStream);
HashedFile hashedFile = getHashedFileForTempFile(temporaryFile);
Document document = prepareXml(fileSystem.getFile(hashedFile.getFilename()));
parseServiceTemplateFromXml(csar, document);
// if plans are not already set parse them directly from the XML
if (hashedFile.getPlans() == null || hashedFile.getPlans().isEmpty()) {
parsePlansFromXml(csar, hashedFile, document);
}
fileSystemRepository.save(hashedFile);
this.csarFile = new CsarFile();
this.csarFile.setCsar(csar);
this.csarFile.setHashedFile(hashedFile);
this.csarFile.setName(name);
this.csarFile.setUploadDate(new Date());
if (null != csarRepository.getLastCsarFile(csar)) {
this.csarFile.setVersion(1 + csarRepository.getLastCsarFile(csar).getVersion());
} else {
this.csarFile.setVersion(1);
}
csarFileRepository.save(csarFile);
csar.getCsarFiles().add(csarFile);
csarRepository.save(csar);
} catch (IllegalStateException | IOException | ParserConfigurationException | PersistenceException
| SAXException | XPathExpressionException e) {
this.addError(e.getMessage());
LOGGER.error(e.getMessage());
return;
}
}
private Document prepareXml(File temporaryFile) throws IOException, ParserConfigurationException, SAXException {
String entryDefinition = Extractor.match(Extractor.unzip(temporaryFile, TOSCA_METADATA_FILEPATH),
ENTRY_DEFINITION_PATTERN);
String xmlData = Extractor.unzip(temporaryFile, entryDefinition);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
return documentBuilder.parse(new ByteArrayInputStream(xmlData.getBytes()));
}
/**
* Parses the serviceTemplate from the xml inside the temporary file and
* updates it in the given CSAR
*
* @param csar
* @param temporaryFile
* @param xpath
* @return
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
* @throws PersistenceException
* if the serviceTemplate doesn't match the serviceTemplate of
* the given CSAR
* @throws XPathExpressionException
*/
private void parseServiceTemplateFromXml(Csar csar, Document document) throws IOException,
ParserConfigurationException, SAXException, PersistenceException, XPathExpressionException {
NodeList elementsByTagNameNS = document.getElementsByTagNameNS(SERVICETEMPLATE_NS, SERVICETEMPLATE_LOCALNAME);
Element serviceTemplate = (Element) elementsByTagNameNS.item(0);
if (null == serviceTemplate) {
throw new PersistenceException("Service Definition does not contain valid ServiceTemplate");
}
String serviceTemplateId = serviceTemplate.getAttribute("id");
String namespace = serviceTemplate.getAttribute("targetNamespace");
if (null == csar.getServiceTemplateId()) {
csar.setServiceTemplateId(serviceTemplateId);
csar.setNamespace(namespace);
LOGGER.info("csar: service template id ({}) and namespace ({}) set", serviceTemplateId, namespace);
} else if (!csar.getServiceTemplateId().equals(serviceTemplateId)
|| (null == csar.getNamespace() && null != namespace && !namespace.equals(null))
|| (null != csar.getNamespace() && !csar.getNamespace().equals(namespace))) {
throw new PersistenceException(String.format(
"File does not match csar service template id (%s: %s) or namespace (%s: %s).",
csar.getServiceTemplateId(), serviceTemplateId, csar.getNamespace(), namespace));
}
}
/**
* Parses the given XML File for Plans and adds them to the given hashedFile
*
* @param csar
* @param hashedFile
* @param xpath
* @param nodeList
* @throws XPathExpressionException
* @throws PersistenceException
*/
private void parsePlansFromXml(Csar csar, HashedFile hashedFile, Document document)
throws XPathExpressionException, PersistenceException {
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression referenceExpression = xpath.compile(XPATH_PLANMODELREFERENCE_REFERENCE);
NodeList elementsByTagNameNS = document.getElementsByTagNameNS(SERVICETEMPLATE_NS, SERVICETEMPLATE_LOCALNAME);
Element serviceTemplate = (Element) elementsByTagNameNS.item(0);
XPathExpression expression = xpath.compile(XPATH_PLANS_FROM_SERVICETEMPLATE);
NodeList nodeList = (NodeList) expression.evaluate(serviceTemplate, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
Element item = (Element) nodeList.item(i);
String planId = item.getAttribute("id");
String fullZipPath = (String) referenceExpression.evaluate(item, XPathConstants.STRING);
String extractedFileName = StringUtils.extractFilenameFromPath(fullZipPath);
String planTypeFromXml = item.getAttribute("planType");
String planNameFromXml = item.getAttribute("name");
Plan.Type planType = null;
if (BUILDPLAN_TYPE_TOSCA.equals(planTypeFromXml)) {
planType = Plan.Type.BUILD;
} else {
planType = Plan.Type.OTHERS;
LOGGER.debug("{} was mapped to PlanType OTHERS", planTypeFromXml);
}
Plan plan = new Plan(hashedFile, planId, planNameFromXml, extractedFileName, planType);
CsarPlanRepository csarPlanRepository = new CsarPlanRepository();
csarPlanRepository.save(plan);
UploadCsarFileService.LOGGER.debug(
"Extracted plan id: '{}' reference: '{}' from csar->id: '{}', ns: '{}' / name: '{}'", planId,
extractedFileName, csar.getId(), csar.getNamespace(), csar.getName());
hashedFile.addPlan(planId, plan);
}
}
/**
* returns hashedFile matching given hash
*
* @param temporaryFile
* @return
* @throws PersistenceException
* @throws ZipException
*/
private HashedFile getHashedFileForTempFile(File temporaryFile) throws PersistenceException {
FileSystemRepository fileSystemRepository = new FileSystemRepository();
FileSystem fileSystem = new FileSystem();
try {
ZipUtils.delete(temporaryFile, CSAR_REPOSITORY_FILENAME);
} catch (ZipException e) {
throw new PersistenceException(e);
}
String hash = fileSystem.generateHash(temporaryFile);
HashedFile hashedFile = null;
if (!fileSystemRepository.containsHash(hash)) {
hashedFile = new HashedFile();
File newFile = fileSystem.saveToFileSystem(temporaryFile);
hashedFile.setFilename(UUID.fromString(newFile.getName()));
hashedFile.setHash(hash);
hashedFile.setSize(newFile.length());
fileSystemRepository.save(hashedFile);
} else {
hashedFile = fileSystemRepository.getByHash(hash);
}
return hashedFile;
}
public CsarFile getResult() {
super.logInvalidResultAccess("getResult");
return this.csarFile;
}
}