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