package alien4cloud.tosca.parser;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.ProviderNotFoundException;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import alien4cloud.tosca.context.ToscaContextual;
import alien4cloud.tosca.model.ArchiveRoot;
import alien4cloud.tosca.model.ToscaMeta;
import alien4cloud.tosca.parser.impl.ErrorCode;
import alien4cloud.tosca.parser.mapping.ToscaMetaMapping;
import alien4cloud.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Parse a TOSCA archive.
*/
@Slf4j
@Component
public class ToscaArchiveParser {
public static final String TOSCA_META_FOLDER_NAME = "TOSCA-Metadata";
public static final String TOSCA_META_FILE_LOCATION = FileSystems.getDefault().getSeparator() + TOSCA_META_FOLDER_NAME
+ FileSystems.getDefault().getSeparator() + "TOSCA.meta";
@Resource
private ToscaMetaMapping toscaMetaMapping;
@Resource
private ToscaParser toscaParser;
/**
* Parse a TOSCA archive and reuse an existing TOSCA Context. Other methods will create an independent context for the parsing.
* Note that a TOSCA Context MUST have been initialized in order to use this method.
*
* @param archiveFile The archive file to parse.
* @return A parsing result that contains the Archive Root and eventual errors and/or warnings.
* @throws ParsingException In case of a severe issue while parsing (incorrect yaml, no tosca file etc.)
*/
public ParsingResult<ArchiveRoot> parseWithExistingContext(Path archiveFile) throws ParsingException {
return parse(archiveFile, false);
}
/**
* Parse a TOSCA archive with a fresh new TOSCA Context.
*
* @param archiveFile The archive file to parse.
* @return A parsing result that contains the Archive Root and eventual errors and/or warnings.
* @throws ParsingException In case of a severe issue while parsing (incorrect yaml, no tosca file etc.)
*/
@ToscaContextual(requiresNew = true)
public ParsingResult<ArchiveRoot> parse(Path archiveFile) throws ParsingException {
return parse(archiveFile, false);
}
/**
* Parse an archive file from a zip.
*
* @param archiveFile The archive file currently zipped.
* @return A parsing result that contains the Archive Root and eventual errors and/or warnings.
* @throws ParsingException In case of a severe issue while parsing (incorrect yaml, no tosca file etc.)
*/
@ToscaContextual(requiresNew = true)
public ParsingResult<ArchiveRoot> parse(Path archiveFile, boolean allowYamlFile) throws ParsingException {
try (FileSystem csarFS = FileSystems.newFileSystem(archiveFile, null)) {
if (Files.exists(csarFS.getPath(TOSCA_META_FILE_LOCATION))) {
return parseFromToscaMeta(csarFS);
}
return parseFromRootDefinitions(csarFS);
} catch (IOException e) {
log.error("Unable to read uploaded archive [" + archiveFile + "]", e);
throw new ParsingException("Archive",
new ParsingError(ErrorCode.FAILED_TO_READ_FILE, "Problem happened while accessing file", null, null, null, archiveFile.toString()));
} catch (ProviderNotFoundException e) {
if (allowYamlFile) {
// the file may be a yaml so let's parse
log.debug("File is not a tosca archive, try to parse it as tosca template. ", e);
return toscaParser.parseFile(archiveFile);
} else {
log.warn("Failed to import archive", e);
throw new ParsingException("Archive", new ParsingError(ErrorCode.ERRONEOUS_ARCHIVE_FILE,
"File is not in good format, only zip file is supported ", null, e.getMessage(), null, null));
}
}
}
// TODO Find a proper way to refactor avoid code duplication with parsing methods from file system
/**
* Parse an archive from a directory, it's very convenient for internal use and test
*
* @param archiveDir the directory which contains the archive
* @return A parsing result that contains the Archive Root and eventual errors and/or warnings.
* @throws ParsingException In case of a severe issue while parsing (incorrect yaml, no tosca file etc.)
*/
@ToscaContextual(requiresNew = true)
public ParsingResult<ArchiveRoot> parseDir(Path archiveDir) throws ParsingException {
Path toscaMetaFile = archiveDir.resolve(TOSCA_META_FILE_LOCATION);
if (Files.exists(toscaMetaFile)) {
return parseFromToscaMeta(archiveDir);
}
return parseFromRootDefinitions(archiveDir);
}
private ParsingResult<ArchiveRoot> parseFromToscaMeta(Path csarPath) throws ParsingException {
YamlSimpleParser<ToscaMeta> parser = new YamlSimpleParser<ToscaMeta>(toscaMetaMapping.getParser());
ParsingResult<ToscaMeta> parsingResult = parser.parseFile(csarPath.resolve(TOSCA_META_FILE_LOCATION));
ArchiveRoot archiveRoot = initFromToscaMeta(parsingResult);
return parseFromToscaMeta(csarPath, parsingResult.getResult(), TOSCA_META_FILE_LOCATION, archiveRoot);
}
private ParsingResult<ArchiveRoot> parseFromToscaMeta(FileSystem csarFS) throws ParsingException {
YamlSimpleParser<ToscaMeta> parser = new YamlSimpleParser<ToscaMeta>(toscaMetaMapping.getParser());
ParsingResult<ToscaMeta> parsingResult = parser.parseFile(csarFS.getPath(TOSCA_META_FILE_LOCATION));
// FIXME shouldn't we check here if the meta parsing went well?
ArchiveRoot archiveRoot = initFromToscaMeta(parsingResult);
return parseFromToscaMeta(csarFS, parsingResult.getResult(), TOSCA_META_FILE_LOCATION, archiveRoot);
}
private ArchiveRoot initFromToscaMeta(ParsingResult<ToscaMeta> toscaMeta) {
ArchiveRoot archiveRoot = new ArchiveRoot();
archiveRoot.getArchive().setName(toscaMeta.getResult().getName());
if (toscaMeta.getResult().getVersion() != null) {
archiveRoot.getArchive().setVersion(toscaMeta.getResult().getVersion());
}
if (toscaMeta.getResult().getCreatedBy() != null) {
archiveRoot.getArchive().setTemplateAuthor(toscaMeta.getResult().getCreatedBy());
}
return archiveRoot;
}
private ParsingResult<ArchiveRoot> parseFromToscaMeta(Path csarPath, ToscaMeta toscaMeta, String metaFileName, ArchiveRoot instance)
throws ParsingException {
if (toscaMeta.getEntryDefinitions() != null) {
return toscaParser.parseFile(csarPath.resolve(toscaMeta.getEntryDefinitions()), instance);
}
throw new ParsingException(metaFileName,
new ParsingError(ErrorCode.ENTRY_DEFINITION_NOT_FOUND, "No entry definitions found in the meta file.", null, null, null, null));
}
private ParsingResult<ArchiveRoot> parseFromToscaMeta(FileSystem csarFS, ToscaMeta toscaMeta, String metaFileName, ArchiveRoot instance)
throws ParsingException {
if (toscaMeta.getEntryDefinitions() != null) {
return toscaParser.parseFile(csarFS.getPath(toscaMeta.getEntryDefinitions()), instance);
}
throw new ParsingException(metaFileName,
new ParsingError(ErrorCode.ENTRY_DEFINITION_NOT_FOUND, "No entry definitions found in the meta file.", null, null, null, null));
}
private ParsingResult<ArchiveRoot> parseFromRootDefinitions(Path csarPath) throws ParsingException {
// load definitions from the archive root
try {
List<Path> yamls = FileUtil.listFiles(csarPath, ".+\\.ya?ml");
if (yamls.size() == 1) {
return toscaParser.parseFile(yamls.get(0));
}
throw new ParsingException("Archive", new ParsingError(ErrorCode.SINGLE_DEFINITION_SUPPORTED,
"Alien only supports archives with a single root definition.", null, null, null, String.valueOf(yamls.size())));
} catch (IOException e) {
throw new ParsingException("Archive", new ParsingError(ErrorCode.FAILED_TO_READ_FILE, "Failed to list root definitions", null, null, null, null));
}
}
private ParsingResult<ArchiveRoot> parseFromRootDefinitions(FileSystem csarFS) throws ParsingException {
// load definitions from the archive root
try {
DefinitionVisitor visitor = new DefinitionVisitor(csarFS);
Files.walkFileTree(csarFS.getPath(csarFS.getSeparator()), EnumSet.noneOf(FileVisitOption.class), 1, visitor);
if (visitor.getDefinitionFiles().size() == 1) {
return toscaParser.parseFile(visitor.getDefinitionFiles().get(0));
}
throw new ParsingException("Archive",
new ParsingError(ErrorCode.SINGLE_DEFINITION_SUPPORTED, "Alien only supports archives with a single root definition.", null, null, null,
"Matching file count in root of " + csarFS + ": " + visitor.getDefinitionFiles().size()));
} catch (IOException e) {
throw new ParsingException("Archive",
new ParsingError(ErrorCode.FAILED_TO_READ_FILE, "Failed to list root definitions", null, null, null, "Error reading " + csarFS + ": " + e));
}
}
}