1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.config.source.yaml;
35
36 import info.magnolia.resourceloader.Resource;
37
38 import java.io.IOException;
39 import java.io.Reader;
40 import java.util.Collections;
41 import java.util.Map;
42 import java.util.Set;
43
44 import javax.inject.Singleton;
45
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.yaml.snakeyaml.Yaml;
49 import org.yaml.snakeyaml.constructor.AbstractConstruct;
50 import org.yaml.snakeyaml.constructor.Constructor;
51 import org.yaml.snakeyaml.error.YAMLException;
52 import org.yaml.snakeyaml.nodes.Node;
53 import org.yaml.snakeyaml.nodes.ScalarNode;
54 import org.yaml.snakeyaml.nodes.Tag;
55
56 import com.google.common.collect.Sets;
57
58
59
60
61 @Singleton
62 public class YamlReader {
63
64 private static final Logger log = LoggerFactory.getLogger(YamlReader.class);
65
66 public Object read(Resource res) throws IOException {
67 return readNoCast(res);
68 }
69
70 public YamlConversionResult readWithDependencies(final Resource resource) throws IOException {
71 final Set<String> dependencies = Sets.newHashSet();
72 final Object result = doReadWithDependencies(resource, dependencies);
73
74 return new YamlConversionResult(result, dependencies);
75 }
76
77 protected Object doReadWithDependencies(final Resource res, Set<String> dependencyAggregator) throws IOException {
78 final Yaml yaml = newSnakeYaml(res, dependencyAggregator);
79 try (final Reader reader = res.openReader()) {
80 return yaml.load(new WhiteSpaceNormalisingReader(reader));
81 } catch (YAMLException ye) {
82 throw new YamlReaderException(ye, res);
83 }
84 }
85
86 public Map<String, Object> readToMap(final Resource resource) throws IOException {
87
88 return (Map<String, Object>) read(resource);
89 }
90
91 protected Object readNoCast(final Resource res) throws IOException {
92 return doReadWithDependencies(res, Sets.<String>newHashSet());
93 }
94
95 protected Yaml newSnakeYaml(Resource yamlResource, Set<String> dependencyAggregator) {
96 return new Yaml(new InclusionSupportingConstructor(this, yamlResource, dependencyAggregator));
97 }
98
99 protected Yaml newSnakeYaml(Resource yamlResource) {
100 return new Yaml(new InclusionSupportingConstructor(this, yamlResource, Sets.<String>newHashSet()));
101 }
102
103 private static class InclusionSupportingConstructor extends Constructor {
104 public InclusionSupportingConstructor(YamlReader yamlReader, Resource baseResource, Set<String> dependencyAggregator) {
105 yamlConstructors.put(new Tag("!include"), new IncludeConstruct(yamlReader, baseResource, dependencyAggregator));
106 }
107 }
108
109
110
111
112
113
114 public static class YamlConversionResult {
115
116 private final Object result;
117 private final Set<String> dependencies;
118
119 public YamlConversionResult(Object result, Set<String> dependencies) {
120 this.result = result;
121 this.dependencies = dependencies;
122 }
123
124 public Set<String> getDependencies() {
125 return dependencies;
126 }
127
128 public Object getResult() {
129 return result;
130 }
131 }
132
133 private static class IncludeConstruct extends AbstractConstruct {
134
135 private final YamlReader yamlReader;
136 private final Resource baseResource;
137 private final Set<String> dependencyAggregator;
138
139 public IncludeConstruct(YamlReader yamlReader, Resource baseResource, Set<String> dependencyAggregator) {
140 this.yamlReader = yamlReader;
141 this.baseResource = baseResource;
142 this.dependencyAggregator = dependencyAggregator;
143 }
144
145 @Override
146 public Object construct(Node node) {
147 if (!(node instanceof ScalarNode)) {
148 throw new IllegalArgumentException("Non-scalar !include: " + node.toString());
149 }
150
151 final ScalarNode scalarNode = (ScalarNode) node;
152 final String path = scalarNode.getValue();
153
154 final Resource includedYamlResource = baseResource.getOrigin().getByPath(path);
155 dependencyAggregator.add(includedYamlResource.getPath());
156
157 try {
158 final YamlConversionResult conversionResult = yamlReader.readWithDependencies(includedYamlResource);
159 dependencyAggregator.addAll(conversionResult.getDependencies());
160 return conversionResult.getResult();
161 } catch (IOException e) {
162 log.error("Failed to process included Yaml resource at [{}] included into [{}] due to: {}. Returning empty map", includedYamlResource.getPath(), baseResource.getPath(), e.getMessage(), e);
163 return Collections.emptyMap();
164 }
165 }
166 }
167 }