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 static info.magnolia.config.maputil.ToMap.toMap;
37
38 import info.magnolia.cms.util.ExceptionUtil;
39 import info.magnolia.config.NamedDefinition;
40 import info.magnolia.config.registry.AbstractDefinitionProviderWrapper;
41 import info.magnolia.config.registry.DefinitionMetadataBuilder;
42 import info.magnolia.config.registry.DefinitionProvider;
43 import info.magnolia.config.registry.DefinitionProviderBuilder;
44 import info.magnolia.config.registry.DefinitionRawView;
45 import info.magnolia.config.source.raw.DefinitionRawViewMapWrapper;
46 import info.magnolia.map2bean.Map2BeanTransformer;
47 import info.magnolia.resourceloader.Resource;
48 import info.magnolia.resourceloader.ResourceOrigin;
49 import info.magnolia.resourceloader.ResourceOrigin.ResourceNotFoundException;
50
51 import java.util.Map;
52 import java.util.Optional;
53 import java.util.Set;
54
55 import org.apache.commons.lang3.exception.ExceptionUtils;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.common.collect.Sets;
60
61
62
63
64
65
66
67 public class YamlDefinitionProvider<T> extends AbstractDefinitionProviderWrapper<T> {
68
69 private static final Logger log = LoggerFactory.getLogger(YamlDefinitionProvider.class);
70 private static final String NAME_PROPERTY = "name";
71 private static final int MODIFICATION_CHECK_THRESHOLD = 1000;
72
73 private final Set<String> dependencies = Sets.newHashSet();
74 private final YamlReader yamlReader;
75 private final YamlConfigurationSource<T> relatedSource;
76 private final String resourcePath;
77 private final ResourceOrigin<?> resourceOrigin;
78 private final Map2BeanTransformer map2BeanTransformer;
79
80 private long lastModificationCheck = -1;
81 private long lastResolved = -1;
82
83 private DefinitionProvider<T> delegate;
84
85 public YamlDefinitionProvider(YamlConfigurationSource<T> relatedSource, Resource yamlResource, Map2BeanTransformer map2BeanTransformer, YamlReader yamlReader) {
86 this.relatedSource = relatedSource;
87 this.map2BeanTransformer = map2BeanTransformer;
88 this.yamlReader = yamlReader;
89 this.resourcePath = yamlResource.getPath();
90 this.resourceOrigin = yamlResource.getOrigin();
91 }
92
93 @Override
94 public long getLastModified() {
95 long lastModified = resourceOrigin.getByPath(resourcePath).getLastModified();
96 for (final String dependencyPath : dependencies) {
97 try {
98 final Resource dependency = resourceOrigin.getByPath(dependencyPath);
99 if (dependency.getLastModified() > lastModified) {
100 log.info("Definition at [{}] will be re-resolved since its dependency at [{}] has changed", dependencyPath, resourcePath);
101 }
102 lastModified = Math.max(dependency.getLastModified(), lastModified);
103 } catch (ResourceNotFoundException e) {
104 log.info("Resource [{}] which is a dependency of [{}], is no longer present. Returning current timestamp as modified date in order to enforce definition re-resolution", dependencyPath, resourcePath);
105 return System.currentTimeMillis();
106 }
107 }
108
109 return lastModified;
110 }
111
112 @Override
113 protected DefinitionProvider<T> getDelegate() {
114 long time = System.currentTimeMillis();
115
116 if (time - lastModificationCheck < MODIFICATION_CHECK_THRESHOLD) {
117 return delegate;
118 }
119
120 lastModificationCheck = time;
121 if (resourceOrigin.hasPath(resourcePath)) {
122 if (getLastModified() > lastResolved) {
123 delegate = resolve();
124 }
125 } else {
126 log.debug("Resource origin no longer contains the definition file [{}], which has most likely been removed, will not attempt to re-resolve definition");
127 }
128 return delegate;
129 }
130
131 private DefinitionProvider<T> resolve() {
132
133 this.dependencies.clear();
134
135 final Resource yamlResource = resourceOrigin.getByPath(resourcePath);
136 final DefinitionProviderBuilder<T> builder = DefinitionProviderBuilder.newBuilder();
137
138 YamlReader.YamlConversionResult yamlConversionResult;
139
140
141 try {
142 yamlConversionResult = yamlReader.readWithDependencies(yamlResource);
143 } catch (Exception e) {
144
145 final String errorMessage =
146 ExceptionUtil.exceptionToWords(
147 Optional
148 .ofNullable(ExceptionUtils.getRootCause(e))
149 .orElse(e));
150
151 builder.addProblem(
152 DefinitionProvider.Problem
153 .severe()
154 .withType(Problem.DefaultTypes.RESOLUTION)
155 .withTitle(String.format("Parsing configuration data from [%s] failed", resourcePath))
156 .withDetails(String.format("Failed to parse YAML file:%n%s", errorMessage))
157 .withRelatedException(e)
158 .build());
159
160 yamlConversionResult = YamlReader.YamlConversionResult.empty();
161 }
162
163
164 yamlConversionResult.getDependencies()
165 .stream()
166 .filter(depPath -> !resourceOrigin.hasPath(depPath))
167 .map(depPath ->
168 Problem
169 .major()
170 .withTitle(String.format("[%s] depends on a missing resource at [%s]", resourcePath, depPath))
171 .withType(Problem.DefaultTypes.RESOLUTION)
172 .build())
173 .forEach(builder::addProblem);
174
175 this.dependencies.addAll(yamlConversionResult.getDependencies());
176
177 final Map<String, Object> map = toMap(yamlConversionResult.getResult());
178 final DefinitionRawView raw = new DefinitionRawViewMapWrapper(map);
179 builder.rawView(raw);
180
181 final DefinitionMetadataBuilder metadataBuilder = relatedSource.createMetadata(yamlResource);
182
183
184
185
186 if (NamedDefinition.class.isAssignableFrom(relatedSource.getRootType())) {
187 final Object nameValue = map.get(NAME_PROPERTY);
188 if (nameValue instanceof String) {
189 metadataBuilder.name((String) nameValue);
190 } else if (nameValue == null) {
191 map.put(NAME_PROPERTY, metadataBuilder.getName());
192 }
193 }
194
195 builder.metadata(metadataBuilder);
196
197 this.lastResolved = System.currentTimeMillis();
198
199 return builder.buildFromTransformationResult(map2BeanTransformer.transform(map, relatedSource.getRootType()));
200 }
201 }