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.config.source.yaml.construct.IncludeFileYamlWithModificationPossibility;
47 import info.magnolia.config.source.yaml.construct.InheritDefinition;
48 import info.magnolia.config.source.yaml.construct.OverrideSource;
49 import info.magnolia.config.source.yaml.construct.WrapMetadata;
50 import info.magnolia.config.source.yaml.dependency.YamlConfigurationDependency;
51 import info.magnolia.map2bean.Map2BeanTransformer;
52 import info.magnolia.module.ModuleRegistry;
53 import info.magnolia.objectfactory.Components;
54 import info.magnolia.resourceloader.Resource;
55 import info.magnolia.resourceloader.ResourceOrigin;
56
57 import java.util.ArrayList;
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Optional;
62 import java.util.function.Consumer;
63 import java.util.function.Function;
64
65 import org.apache.commons.lang3.exception.ExceptionUtils;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68 import org.yaml.snakeyaml.constructor.Construct;
69
70
71
72
73
74
75
76 public class YamlDefinitionProvider<T> extends AbstractDefinitionProviderWrapper<T> {
77
78 private static final Logger log = LoggerFactory.getLogger(YamlDefinitionProvider.class);
79 private static final String NAME_PROPERTY = "name";
80 private static final int MODIFICATION_CHECK_THRESHOLD = 1000;
81
82 private final YamlConfigurationSource<T> relatedSource;
83 private final YamlReader yamlReader;
84 private final String resourcePath;
85 private final ResourceOrigin<?> resourceOrigin;
86 private final ModuleRegistry moduleRegistry;
87 private final Map<String, Function<Consumer<DefinitionProvider.Problem>, Construct>> customConstructs;
88 private final Map<String, Function<Consumer<DefinitionProvider.Problem>, Construct>> customMultiConstructs;
89 private final Map2BeanTransformer map2BeanTransformer;
90 private final List<YamlConfigurationDependency> dependencies = new ArrayList<>();
91
92 private long lastModificationCheck = -1;
93 private long lastResolved = -1;
94 private boolean missingDependencies;
95
96 private DefinitionProvider<T> delegate;
97
98 public YamlDefinitionProvider(
99 YamlConfigurationSource<T> relatedSource,
100 Resource yamlResource,
101 Map2BeanTransformer map2BeanTransformer,
102 YamlReader yamlReader,
103 ModuleRegistry moduleRegistry,
104 Map<String, Function<Consumer<DefinitionProvider.Problem>, Construct>> customConstructs,
105 Map<String, Function<Consumer<DefinitionProvider.Problem>, Construct>> customMultiConstructs) {
106 this.relatedSource = relatedSource;
107 this.map2BeanTransformer = map2BeanTransformer;
108 this.resourcePath = yamlResource.getPath();
109 this.resourceOrigin = yamlResource.getOrigin();
110 this.yamlReader = yamlReader;
111 this.moduleRegistry = moduleRegistry;
112 this.customConstructs = customConstructs;
113 this.customMultiConstructs = customMultiConstructs;
114 }
115
116
117
118
119 @Deprecated
120 public YamlDefinitionProvider(YamlConfigurationSource<T> relatedSource, Resource yamlResource, Map2BeanTransformer map2BeanTransformer, YamlReader yamlReader, ModuleRegistry moduleRegistry) {
121 this(relatedSource, yamlResource, map2BeanTransformer, yamlReader, moduleRegistry, new HashMap<>(), new HashMap<>());
122 }
123
124
125
126
127 @Deprecated
128 public YamlDefinitionProvider(YamlConfigurationSource<T> relatedSource, Resource yamlResource, Map2BeanTransformer map2BeanTransformer, YamlReader yamlReader) {
129 this(relatedSource, yamlResource, map2BeanTransformer, yamlReader, Components.getComponent(ModuleRegistry.class));
130 }
131
132 @Override
133 public long getLastModified() {
134 long lastModified = resourceOrigin.getByPath(resourcePath).getLastModified();
135 return dependencies
136 .stream()
137 .map(YamlConfigurationDependency::getLastModified)
138 .reduce(lastModified, Math::max);
139 }
140
141 @Override
142 protected DefinitionProvider<T> getDelegate() {
143 long time = System.currentTimeMillis();
144
145 if (time - lastModificationCheck < MODIFICATION_CHECK_THRESHOLD) {
146 return delegate;
147 }
148
149 lastModificationCheck = time;
150 if (resourceOrigin.hasPath(resourcePath)) {
151 if (missingDependencies || getLastModified() > lastResolved) {
152 delegate = resolve();
153 }
154 } else {
155 log.debug("Resource origin no longer contains the definition file [{}], which has most likely been removed, will not attempt to re-resolve definition");
156 }
157 return delegate;
158 }
159
160 private DefinitionProvider<T> resolve() {
161
162 this.dependencies.clear();
163
164 final Resource yamlResource = resourceOrigin.getByPath(resourcePath);
165 final DefinitionProviderBuilder<T> builder = relatedSource.getRegistry().newDefinitionProviderBuilder();
166
167 final DefinitionMetadataBuilder metadataBuilder = relatedSource.createMetadata(yamlResource);
168 String currentModule = metadataBuilder.getModule();
169
170 final Consumer<Problem> problemCollector = builder::addProblem;
171
172
173 yamlReader.registerCustomConstruct(OverrideSource.TAG, new OverrideSource(problemCollector));
174 yamlReader.registerCustomMultiConstruct(InheritDefinition.TAG_PREFIX, new InheritDefinition<>(this.relatedSource.getRegistry(), moduleRegistry, currentModule, metadataBuilder.getName(), problemCollector));
175 yamlReader.registerCustomMultiConstruct(IncludeFileYamlWithModificationPossibility.TAG_PREFIX, new IncludeFileYamlWithModificationPossibility(yamlReader, resourceOrigin, problemCollector));
176 yamlReader.registerCustomMultiConstruct(WrapMetadata.TAG_PREFIX, new WrapMetadata(problemCollector));
177
178 customConstructs.forEach((key, value) -> yamlReader.registerCustomConstruct(key, value.apply(problemCollector)));
179 customMultiConstructs.forEach((key, value) -> yamlReader.registerCustomMultiConstruct(key, value.apply(problemCollector)));
180
181
182 Consumer<WrapMetadata.Metadata> metadataConsumer = YamlMetadataConsumer.Nested.withSingleDelegate("deprecated",
183 new DeprecationMetadataConsumer(metadataBuilder::deprecation));
184 MetadataProcessor metadataProcessor = new MetadataProcessor(metadataConsumer);
185
186 YamlReader.YamlConversionResult yamlConfigData;
187
188
189 try {
190 yamlConfigData = yamlReader.readWithDependencies(yamlResource);
191 } catch (Exception e) {
192
193 final String errorMessage =
194 ExceptionUtil.exceptionToWords(
195 Optional
196 .ofNullable(ExceptionUtils.getRootCause(e))
197 .orElse(e));
198
199 builder.addProblem(
200 DefinitionProvider.Problem
201 .severe()
202 .withType(Problem.DefaultTypes.RESOLUTION)
203 .withTitle(String.format("Parsing configuration data from [%s] failed", resourcePath))
204 .withDetails(String.format("Failed to parse YAML file:%n%s", errorMessage))
205 .withRelatedException(e)
206 .build());
207 yamlConfigData = YamlReader.YamlConversionResult.empty();
208 }
209
210 dependencies.addAll(yamlConfigData.getDependencies());
211 missingDependencies = dependencies
212 .stream()
213 .anyMatch(yamlConfigurationDependency -> !yamlConfigurationDependency.exists());
214
215 final Map<String, Object> map = toMap(yamlConfigData.getResult());
216 final Map<String, Object> filteredMap = metadataProcessor.process(map);
217 final DefinitionRawView raw = new DefinitionRawViewMapWrapper(filteredMap);
218 builder.rawView(raw);
219
220 Class definitionType = metadataBuilder.getType().baseClass();
221
222
223
224
225 if (NamedDefinition.class.isAssignableFrom(definitionType)) {
226 final Object nameValue = filteredMap.get(NAME_PROPERTY);
227 if (nameValue instanceof String) {
228 metadataBuilder.name((String) nameValue);
229 } else if (nameValue == null) {
230 filteredMap.put(NAME_PROPERTY, metadataBuilder.getName());
231 }
232 }
233
234 builder.metadata(metadataBuilder);
235
236 this.lastResolved = System.currentTimeMillis();
237
238 return builder.buildFromTransformationResult(map2BeanTransformer.transform(filteredMap, definitionType));
239 }
240 }