package alien4cloud.tosca.parser.mapping.generator; import java.io.IOException; import java.nio.file.Path; import java.util.AbstractMap; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.Resource; import org.springframework.aop.framework.Advised; import org.springframework.context.ApplicationContext; import org.springframework.core.io.FileSystemResource; import org.springframework.stereotype.Component; import org.yaml.snakeyaml.nodes.*; import com.google.common.collect.Maps; import alien4cloud.tosca.parser.*; import alien4cloud.tosca.parser.impl.ErrorCode; import alien4cloud.tosca.parser.impl.base.BaseParserFactory; import alien4cloud.tosca.parser.impl.base.ScalarParser; import alien4cloud.tosca.parser.impl.base.TypeNodeParser; import lombok.extern.slf4j.Slf4j; /** * Load type mapping definition from yaml and add it to the type mapping registry. */ @Slf4j @Component public class MappingGenerator implements INodeParser<Map<String, INodeParser>> { @Resource private ApplicationContext applicationContext; @Resource private BaseParserFactory baseParserFactory; private Map<String, INodeParser> parsers = Maps.newHashMap(); private Map<String, IMappingBuilder> mappingBuilders = Maps.newHashMap(); @PostConstruct public void initialize() { Map<String, INodeParser> contextParsers = applicationContext.getBeansOfType(INodeParser.class, false, true); // register parsers based on their class name. for (INodeParser parser : contextParsers.values()) { String className; if (parser instanceof Advised) { className = ((Advised) parser).getTargetSource().getTargetClass().getName(); } else { className = parser.getClass().getName(); } parsers.put(className, parser); } Map<String, IMappingBuilder> contextMappingBuilders = applicationContext.getBeansOfType(IMappingBuilder.class); for (IMappingBuilder mappingBuilder : contextMappingBuilders.values()) { mappingBuilders.put(mappingBuilder.getKey(), mappingBuilder); } } public Map<String, INodeParser> process(Path resourceLocation) throws ParsingException { org.springframework.core.io.Resource resource = new FileSystemResource(resourceLocation.toFile()); return process(resource); } public Map<String, INodeParser> process(String resourceLocation) throws ParsingException { org.springframework.core.io.Resource resource = applicationContext.getResource(resourceLocation); return process(resource); } private Map<String, INodeParser> process(org.springframework.core.io.Resource resource) throws ParsingException { YamlSimpleParser<Map<String, INodeParser>> nodeParser = new YamlSimpleParser<>(this); try { ParsingResult<Map<String, INodeParser>> result = nodeParser.parseFile(resource.getURI().toString(), resource.getFilename(), resource.getInputStream(), null); if (result.getContext().getParsingErrors().isEmpty()) { return result.getResult(); } throw new ParsingException(resource.getFilename(), result.getContext().getParsingErrors()); } catch (IOException e) { log.error("Failed to open stream", e); throw new ParsingException(resource.getFilename(), new ParsingError(ErrorCode.MISSING_FILE, "Unable to load file.", null, e.getMessage(), null, resource.getFilename())); } } public Map<String, INodeParser> parse(Node node, ParsingContextExecution context) { Map<String, INodeParser> parsers = Maps.newHashMap(); if (node instanceof SequenceNode) { SequenceNode types = (SequenceNode) node; for (Node mapping : types.getValue()) { Map.Entry<String, INodeParser<?>> entry = processTypeMapping(mapping, context); if (entry != null) { parsers.put(entry.getKey(), entry.getValue()); } } } else { context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Mapping should be a sequence of type mappings", node.getStartMark(), "Actually was " + node.getClass().getSimpleName(), node.getEndMark(), "")); } return parsers; } private Map.Entry<String, INodeParser<?>> processTypeMapping(Node node, ParsingContextExecution context) { try { return doProcessTypeMapping(node, context); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { log.error("Failed to load class while parsing mapping", e); context.getParsingErrors() .add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Unable to load class", node.getStartMark(), e.getMessage(), node.getEndMark(), "")); return null; } } private Map.Entry<String, INodeParser<?>> doProcessTypeMapping(Node node, ParsingContextExecution context) throws ClassNotFoundException, IllegalAccessException, InstantiationException { // process the mapping of a given type if (node instanceof MappingNode) { MappingNode mapping = (MappingNode) node; String yamlType = null; INodeParser<?> parser = null; for (NodeTuple tuple : mapping.getValue()) { // the first in a node mapping must be the yaml key for the type and the definition of the java type to map to or a direct reference to a // parser. if (yamlType == null) { yamlType = ParserUtils.getScalar(tuple.getKeyNode(), context); // it's value design the java parser to be used to build the java type. String type = ParserUtils.getScalar(tuple.getValueNode(), context); if (type.startsWith("__")) { parser = getWrapperParser(type, mapping, context); return new AbstractMap.SimpleEntry<String, INodeParser<?>>(yamlType, parser); } // try to find a registered parser for the type. Direct parser reference. parser = this.parsers.get(type); if (parser != null) { log.debug("Mapping yaml type <" + yamlType + "> using parser <" + type + ">"); return new AbstractMap.SimpleEntry<String, INodeParser<?>>(yamlType, parser); } // The value for this mapping is not an exising parser, that may be either a java type either the reference to a mapping builder (a // collection for example). IMappingBuilder builder = mappingBuilders.get(type); if (builder != null) { mapping.getValue().add(0, new NodeTuple(new ScalarNode(new Tag(builder.getKey()), builder.getKey(), tuple.getKeyNode().getStartMark(), tuple.getKeyNode().getEndMark(), 'c'), tuple.getValueNode())); // there is a builder parser = builder.buildMapping(mapping, context).getParser(); return new AbstractMap.SimpleEntry<String, INodeParser<?>>(yamlType, parser); } else { // If the type doesn't design a referenced parser then we should try to build it. parser = buildTypeNodeParser(yamlType, type); } } else { // process a mapping map(tuple, (TypeNodeParser) parser, context); } } return new AbstractMap.SimpleEntry<String, INodeParser<?>>(yamlType, parser); } else { context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Unable to process type mapping.", node.getStartMark(), "Mapping must be defined using a mapping node.", node.getEndMark(), "")); } return null; } private TypeNodeParser<?> buildTypeNodeParser(String yamlType, String javaType) throws ClassNotFoundException { String realJavaType = javaType; Class<?> javaClass = Class.forName(realJavaType); log.debug("Mapping yaml type <" + yamlType + "> to class <" + realJavaType + ">"); return baseParserFactory.getTypeNodeParser(javaClass, yamlType); } private INodeParser<?> getWrapperParser(String wrapperKey, MappingNode mapping, ParsingContextExecution context) { IMappingBuilder builder = this.mappingBuilders.get(wrapperKey.substring(2)); return builder.buildMapping(mapping, context).getParser(); } private void map(NodeTuple tuple, TypeNodeParser<?> parser, ParsingContextExecution context) { String key = ParserUtils.getScalar(tuple.getKeyNode(), context); // position mapping allows handling of yaml mapping where the key and the value are both actually 'values' of some fields (mixing 2 fields in a single // one in yaml). int positionMappingIndex = positionMappingIndex(key); if (positionMappingIndex > -1) { // if the key is format __x where x is number process a position based mapping (__0 first element of the tupple) mapPositionMapping(positionMappingIndex, tuple.getValueNode(), parser, context); } else { // if not just process a standard mapping where key is the yaml key. MappingTarget mappingTarget = getMappingTarget(tuple.getValueNode(), context); if (mappingTarget != null) { parser.getYamlToObjectMapping().put(key, mappingTarget); } } } private MappingTarget getMappingTarget(Node mappingNode, ParsingContextExecution context) { if (mappingNode instanceof ScalarNode) { // if the mapping reference a scalar we just map it to a scalar definition. String value = ParserUtils.getScalar(mappingNode, context); return new MappingTarget(value, parsers.get(ScalarParser.class.getName())); } else if (mappingNode instanceof MappingNode) { // if this is a mapping node then it can be either a collection or reference to a complex object mapping (through reference parser). return mapMappingNode((MappingNode) mappingNode, context); } return null; } private int positionMappingIndex(String key) { if (key.startsWith("__")) { try { int position = Integer.valueOf(key.substring(2)); return position; } catch (NumberFormatException e) { // not a position mapping return -1; } } return -1; } private void mapPositionMapping(Integer index, Node positionMapping, TypeNodeParser<?> parser, ParsingContextExecution context) { if (positionMapping instanceof MappingNode) { MappingNode mappingNode = (MappingNode) positionMapping; String key = null; MappingTarget valueMappingTarget = null; for (NodeTuple tuple : mappingNode.getValue()) { String tupleKey = ParserUtils.getScalar(tuple.getKeyNode(), context); if (tupleKey.equals("key")) { key = ParserUtils.getScalar(tuple.getValueNode(), context); } else if (tupleKey.equals("value")) { valueMappingTarget = getMappingTarget(tuple.getValueNode(), context); } else { context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Unknown key for position mapping.", tuple.getKeyNode().getStartMark(), tupleKey, tuple.getKeyNode().getEndMark(), "")); } } if (valueMappingTarget == null) { return; } if (key == null) { parser.getYamlOrderedToObjectMapping().put(index, valueMappingTarget); } else { parser.getYamlOrderedToObjectMapping().put(index, new KeyValueMappingTarget(key, valueMappingTarget.getPath(), valueMappingTarget.getParser())); } } else { context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Position mapping must be a mapping node with key and value fields.", positionMapping.getStartMark(), "", positionMapping.getEndMark(), "")); } } private MappingTarget mapMappingNode(MappingNode mappingNode, ParsingContextExecution context) { String key = ParserUtils.getScalar(mappingNode.getValue().get(0).getKeyNode(), context); IMappingBuilder mappingBuilder = mappingBuilders.get(key); if (mappingBuilder != null) { log.debug("Mapping yaml key <" + key + "> using mapping builder " + mappingBuilder.getClass().getName()); return mappingBuilder.buildMapping(mappingNode, context); } context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "No mapping target found for key", mappingNode.getValue().get(0).getKeyNode().getStartMark(), key, mappingNode.getValue().get(0).getKeyNode().getEndMark(), "")); return null; } }