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.construct;
35
36 import static info.magnolia.config.source.yaml.dependency.YamlDependencyResoutionProblemType.DEPENDENCY_RESOLUTION;
37 import static java.util.stream.Collectors.toList;
38
39 import info.magnolia.config.maputil.ConfigurationMapOverlay;
40 import info.magnolia.config.maputil.ToMap;
41 import info.magnolia.config.registry.DefinitionProvider.Problem;
42 import info.magnolia.config.registry.Registry;
43 import info.magnolia.config.source.yaml.MgnlYamlConstructor;
44 import info.magnolia.config.source.yaml.dependency.DefinitionDependency;
45 import info.magnolia.module.ModuleRegistry;
46 import info.magnolia.module.model.DependencyDefinition;
47
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.Map;
51 import java.util.function.Consumer;
52 import java.util.regex.Matcher;
53 import java.util.regex.Pattern;
54 import java.util.stream.Collectors;
55
56 import org.apache.commons.lang3.StringUtils;
57 import org.yaml.snakeyaml.nodes.Node;
58 import org.yaml.snakeyaml.nodes.ScalarNode;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 public final class InheritDefinition<T> extends MgnlYamlConstruct {
75
76 public static final String TAG_PREFIX = "!inherit:";
77
78 private static final Pattern INHERITANCE_PATTERN = Pattern.compile("^!inherit:(?<definitionreference>.+)$");
79
80 private final Registry<T> registry;
81 private final ModuleRegistry moduleRegistry;
82
83 private final String sourceModule;
84 private final String defaultName;
85
86 public InheritDefinition(Registry<T> targetRegistry, ModuleRegistry moduleRegistry, String currentModule, String defaultName, Consumer<Problem> problemCollector) {
87 super(problemCollector);
88 registry = targetRegistry;
89 this.moduleRegistry = moduleRegistry;
90 this.sourceModule = currentModule;
91 this.defaultName = defaultName;
92 }
93
94 @Override
95 public Object construct(Node node) {
96 final MgnlYamlConstructor constructor = getConstructor();
97 final Map<String, Object> baseData = ToMap.toMap(constructor.getConstructByNodeType(node).construct(node));
98
99 if (node instanceof ScalarNode) {
100 final String scalarValue = ((ScalarNode) node).getValue();
101 if (!StringUtils.isEmpty(scalarValue)) {
102 reportProblem(
103 Problem
104 .minor()
105 .withType(DEPENDENCY_RESOLUTION)
106 .withTitle("Redundant inheritance syntax used")
107 .withDetails(String.format("Scalar value [%s] of node [%s] will be ignored due to !inherit semantics",
108 scalarValue, node.getNodeId()))
109 .build());
110 }
111 }
112
113 final Matcher definitionReferenceMatcher = INHERITANCE_PATTERN.matcher(node.getTag().getValue());
114 if (!definitionReferenceMatcher.matches()) {
115 reportProblem(
116 Problem
117 .severe()
118 .withType(DEPENDENCY_RESOLUTION)
119 .withTitle("Mis-configured definition dependency")
120 .withDetails(String.format("Tag [%s] does not match the inheritance pattern !inherit:<type>:<referenceId>",
121 node.getTag().getValue()))
122 .build());
123 return baseData;
124 }
125
126 final String definitionReference = definitionReferenceMatcher.group("definitionreference");
127 final DefinitionDependency dependency =
128 DefinitionDependency
129 .resolve()
130 .withRegistry(registry)
131 .withDefinitionReference(definitionReference)
132 .withProblemCollector(this::reportProblem)
133 .build();
134
135 constructor.getDependencyAggregator().addDependency(dependency);
136
137 if (!dependency.exists()) {
138 final String moduleDependencies = getSourceModuleDependencies().stream().collect(Collectors.joining(", "));
139 reportProblem(
140 Problem
141 .severe()
142 .withType(DEPENDENCY_RESOLUTION)
143 .withTitle("Missing definition dependency")
144 .withDetails(String.format("Inherited definition at [%s] either does not exist or is not loaded because of the missing module dependency in module [%s], which currently depends only on [%s]",
145 definitionReference, sourceModule, moduleDependencies))
146 .build());
147
148 return baseData;
149 }
150
151 analyseModuleDependencyCorrectness(definitionReference, dependency);
152
153
154
155
156
157
158
159
160
161
162
163 baseData.putIfAbsent("name", defaultName);
164
165 return ConfigurationMapOverlay
166 .of(dependency.readData())
167 .by(baseData)
168 .at("/")
169 .overlay();
170
171 }
172
173 private void analyseModuleDependencyCorrectness(String definitionReference, DefinitionDependency dependency) {
174 final String dependencyModule = dependency.resolveDefinitionProvider().getMetadata().getModule();
175
176 String moduleDependencyProblemStatement = null;
177 if (StringUtils.equals(dependencyModule, sourceModule)) {
178 moduleDependencyProblemStatement =
179 String.format("Dependency definition [%s] resides at the same module [%s] as the dependent one, which might lead to inconsistencies",
180 definitionReference, dependencyModule);
181 } else if (isUndeclaredModuleDependency(sourceModule, dependencyModule)) {
182 moduleDependencyProblemStatement =
183 String.format("Dependency definition [%s] resides at the module [%s] not dependent by the source definition's module [%s]",
184 definitionReference, dependencyModule, sourceModule);
185 }
186
187 if (StringUtils.isNotBlank(moduleDependencyProblemStatement)) {
188 reportProblem(
189 Problem
190 .major()
191 .withType(DEPENDENCY_RESOLUTION)
192 .withTitle("Definition inherited with module dependency problem")
193 .withDetails(moduleDependencyProblemStatement)
194 .build());
195 }
196 }
197
198 private Collection<String> getSourceModuleDependencies() {
199 if (this.moduleRegistry.isModuleRegistered(this.sourceModule)) {
200 return this.moduleRegistry.getDefinition(this.sourceModule)
201 .getDependencies().stream()
202 .map(DependencyDefinition::getName)
203 .collect(toList());
204 }
205 return Collections.emptySet();
206 }
207
208 private boolean isUndeclaredModuleDependency(String currentModule, String dependencyModule) {
209 return !moduleRegistry.isModuleRegistered(currentModule) ||
210 moduleRegistry
211 .getDefinition(currentModule)
212 .getDependencies()
213 .stream()
214 .map(DependencyDefinition::getName)
215 .noneMatch(name -> name.equals(dependencyModule));
216 }
217 }