package org.alien4cloud.tosca.catalog;
import static alien4cloud.utils.AlienUtils.safe;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Map;
import javax.annotation.Resource;
import org.alien4cloud.tosca.model.definitions.AbstractArtifact;
import org.alien4cloud.tosca.model.definitions.DeploymentArtifact;
import org.alien4cloud.tosca.model.definitions.Interface;
import org.alien4cloud.tosca.model.templates.AbstractTemplate;
import org.alien4cloud.tosca.model.templates.NodeTemplate;
import org.alien4cloud.tosca.model.templates.RelationshipTemplate;
import org.alien4cloud.tosca.model.templates.Topology;
import org.alien4cloud.tosca.model.types.AbstractInstantiableToscaType;
import org.alien4cloud.tosca.model.types.AbstractToscaType;
import org.apache.commons.lang.StringUtils;
import alien4cloud.repository.services.RepositoryService;
import alien4cloud.topology.TopologyUtils;
import alien4cloud.tosca.model.ArchiveRoot;
import alien4cloud.tosca.parser.ParsingError;
import alien4cloud.tosca.parser.ParsingErrorLevel;
import alien4cloud.tosca.parser.ParsingResult;
import alien4cloud.tosca.parser.impl.ErrorCode;
import alien4cloud.utils.FileUtil;
import alien4cloud.utils.InputArtifactUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Post process an archive after parsing to inject workspace and check if artifacts exists.
*/
@Slf4j
public abstract class AbstractArchivePostProcessor {
@Resource
private RepositoryService repositoryService;
public interface ArchivePathChecker extends AutoCloseable {
boolean exists(String artifactReference);
void close();
}
/**
* Post process the archive: For every definition of the model it fills the id fields in the TOSCA elements from the key of the elements map.
*
* @param parsedArchive The archive to post process
*/
protected ParsingResult<ArchiveRoot> doProcess(Path archive, ParsingResult<ArchiveRoot> parsedArchive, String workspace) {
String hash = FileUtil.deepSHA1(archive);
parsedArchive.getResult().getArchive().setHash(hash);
parsedArchive.getResult().getArchive().setWorkspace(workspace);
// FIXME how should we manage hash for the topology tempalte ?
processTopology(parsedArchive.getResult().getArchive().getWorkspace(), parsedArchive);
// Injext archive hash in every indexed node type.
processTypes(parsedArchive.getResult());
processArtifacts(archive, parsedArchive);
return parsedArchive;
}
/**
* Inject the workspace in indexed elements.
*
* @param archiveRoot The archive out of parsing
*/
private void processTypes(ArchiveRoot archiveRoot) {
processTypes(archiveRoot.getArchive().getWorkspace(), archiveRoot.getArtifactTypes());
processTypes(archiveRoot.getArchive().getWorkspace(), archiveRoot.getCapabilityTypes());
processTypes(archiveRoot.getArchive().getWorkspace(), archiveRoot.getDataTypes());
processTypes(archiveRoot.getArchive().getWorkspace(), archiveRoot.getNodeTypes());
processTypes(archiveRoot.getArchive().getWorkspace(), archiveRoot.getRelationshipTypes());
}
private void processTypes(String workspace, Map<String, ? extends AbstractToscaType> elements) {
for (AbstractToscaType element : safe(elements).values()) {
element.setWorkspace(workspace);
}
}
private void processTopology(String workspace, ParsingResult<ArchiveRoot> parsedArchive) {
Topology topology = parsedArchive.getResult().getTopology();
if (topology != null) {
topology.setArchiveName(parsedArchive.getResult().getArchive().getName());
topology.setArchiveVersion(parsedArchive.getResult().getArchive().getVersion());
topology.setWorkspace(workspace);
TopologyUtils.normalizeAllNodeTemplateName(topology, parsedArchive);
}
}
private void processLocalArtifact(ArchivePathChecker archivePathResolver, AbstractArtifact artifact, ParsingResult<ArchiveRoot> parsedArchive) {
if (artifact.getArtifactRef() == null || artifact.getArtifactRef().isEmpty()) {
return; // Artifact may not be specified and may be set in alien4cloud.
}
if (!archivePathResolver.exists(artifact.getArtifactRef())) {
parsedArchive.getContext().getParsingErrors().add(new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.INVALID_ARTIFACT_REFERENCE,
"Invalid artifact reference", null, "CSAR's artifact does not exist", null, artifact.getArtifactRef()));
}
}
private boolean hasInputArtifacts(ParsingResult<ArchiveRoot> parsedArchive) {
return parsedArchive.getResult().getTopology() != null && parsedArchive.getResult().getTopology().getInputArtifacts() != null;
}
private void processArtifact(ArchivePathChecker archivePathResolver, AbstractArtifact artifact, ParsingResult<ArchiveRoot> parsedArchive) {
if (!(parsedArchive.getResult().getArchive().getName().equals(artifact.getArchiveName())
&& parsedArchive.getResult().getArchive().getVersion().equals(artifact.getArchiveVersion()))) {
// if the artifact is not defined in the current archive then we don't have to perform validation.
return;
}
// Else also inject the workspace
String inputArtifactId = InputArtifactUtil.getInputArtifactId(artifact);
if (StringUtils.isNotBlank(inputArtifactId) && hasInputArtifacts(parsedArchive)) {
if (!parsedArchive.getResult().getTopology().getInputArtifacts().containsKey(inputArtifactId)) {
// The input artifact id does not exist in the topology's definition
parsedArchive.getContext().getParsingErrors().add(new ParsingError(ErrorCode.INVALID_ARTIFACT_REFERENCE, "Invalid artifact reference", null,
"Artifact's reference " + artifact.getArtifactRef() + " is not valid", null, artifact.getArtifactRef()));
}
return;
}
URL artifactURL = null;
if (artifact.getRepositoryName() == null) {
// Short notation
try {
// Test if it's an URL
artifactURL = new URL(artifact.getArtifactRef());
} catch (MalformedURLException e) {
if (log.isDebugEnabled()) {
log.debug("Archive artifact validation - Processing local artifact {}", artifact);
}
// Not a URL then must be a relative path to a file inside the csar
processLocalArtifact(archivePathResolver, artifact, parsedArchive);
return;
}
}
if (log.isDebugEnabled()) {
log.debug("Archive artifact validation - Processing remote artifact {}", artifact);
}
if (!repositoryService.canResolveArtifact(artifact.getArtifactRef(), artifact.getRepositoryURL(), artifact.getArtifactRepository(),
artifact.getRepositoryCredential())) {
if (artifactURL != null) {
try (InputStream ignored = artifactURL.openStream()) {
// In a best effort try in a generic manner to obtain the artifact
} catch (IOException e) {
parsedArchive.getContext().getParsingErrors().add(new ParsingError(ErrorCode.INVALID_ARTIFACT_REFERENCE, "Invalid artifact reference", null,
"Artifact's reference " + artifact.getArtifactRef() + " is not valid", null, artifact.getArtifactRef()));
}
} else {
parsedArchive.getContext().getParsingErrors().add(new ParsingError(ErrorCode.UNRESOLVED_ARTIFACT, "Unresolved artifact", null,
"Artifact " + artifact.getArtifactRef() + " cannot be resolved", null, artifact.getArtifactRef()));
}
}
}
private void processInterfaces(ArchivePathChecker archivePathResolver, Map<String, Interface> interfaceMap, ParsingResult<ArchiveRoot> parsedArchive) {
if (interfaceMap != null) {
interfaceMap.values().stream().filter(interfazz -> interfazz.getOperations() != null).forEach(interfazz -> interfazz.getOperations().values()
.stream().filter(operation -> operation.getImplementationArtifact() != null).forEach(operation -> {
processArtifact(archivePathResolver, operation.getImplementationArtifact(), parsedArchive);
for (DeploymentArtifact artifact : safe(operation.getDependencies())) {
processArtifact(archivePathResolver, artifact, parsedArchive);
}
}));
}
}
private <T extends AbstractInstantiableToscaType> void processTypes(ArchivePathChecker archivePathResolver, Map<String, T> types,
ParsingResult<ArchiveRoot> parsedArchive) {
if (types != null) {
for (T type : types.values()) {
if (type.getArtifacts() != null) {
type.getArtifacts().values().stream().filter(artifact -> artifact != null)
.forEach(artifact -> processArtifact(archivePathResolver, artifact, parsedArchive));
}
processInterfaces(archivePathResolver, type.getInterfaces(), parsedArchive);
}
}
}
private <T extends AbstractTemplate> void processTemplate(ArchivePathChecker archivePathResolver, T template, ParsingResult<ArchiveRoot> parsedArchive) {
if (template.getArtifacts() != null) {
template.getArtifacts().values().stream().filter(artifact -> artifact != null)
.forEach(artifact -> processArtifact(archivePathResolver, artifact, parsedArchive));
}
processInterfaces(archivePathResolver, template.getInterfaces(), parsedArchive);
}
private void processInputArtifact(ArchivePathChecker archivePathResolver, ParsingResult<ArchiveRoot> parsedArchive) {
if (hasInputArtifacts(parsedArchive)) {
parsedArchive.getResult().getTopology().getInputArtifacts().values().stream()
// If artifact reference is null it means it's to be uploaded with the GUI
.filter(inputArtifact -> StringUtils.isNotBlank(inputArtifact.getArtifactRef()))
.forEach(inputArtifact -> this.processArtifact(archivePathResolver, inputArtifact, parsedArchive));
}
}
private void processArtifacts(final Path archive, ParsingResult<ArchiveRoot> parsedArchive) {
try (ArchivePathChecker archivePathResolver = createPathChecker(archive)) {
// Check if input artifacts are correctly set
processInputArtifact(archivePathResolver, parsedArchive);
// Process deployment artifact / implementation artifact for types
processTypes(archivePathResolver, parsedArchive.getResult().getNodeTypes(), parsedArchive);
processTypes(archivePathResolver, parsedArchive.getResult().getRelationshipTypes(), parsedArchive);
// Process topology
Topology topology = parsedArchive.getResult().getTopology();
if (topology != null && topology.getNodeTemplates() != null) {
for (NodeTemplate nodeTemplate : topology.getNodeTemplates().values()) {
processTemplate(archivePathResolver, nodeTemplate, parsedArchive);
if (nodeTemplate.getRelationships() != null) {
for (RelationshipTemplate relationshipTemplate : nodeTemplate.getRelationships().values()) {
processTemplate(archivePathResolver, relationshipTemplate, parsedArchive);
}
}
}
}
}
}
/**
* Create an instance of ArchivePathChecker to check if local artifacts indeed exists in archive.
*
* @param archive The archive path.
* @return An instance of archive path checker.
*/
protected abstract ArchivePathChecker createPathChecker(Path archive);
}