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.resourceloader.util.Functions.pathMatches;
37 import static java.util.Comparator.comparing;
38 import static java.util.stream.Collectors.toList;
39
40 import info.magnolia.config.registry.DefinitionMetadata;
41 import info.magnolia.config.registry.DefinitionMetadataBuilder;
42 import info.magnolia.config.registry.DefinitionProvider;
43 import info.magnolia.config.registry.Registry;
44 import info.magnolia.config.source.ConfigurationSource;
45 import info.magnolia.config.source.ConfigurationSourceType;
46 import info.magnolia.config.source.ConfigurationSourceTypes;
47 import info.magnolia.config.source.yaml.decoration.FileDefinitionDecorator;
48 import info.magnolia.config.source.yaml.decoration.FileDefinitionDecoratorResolver;
49 import info.magnolia.module.ModuleRegistry;
50 import info.magnolia.module.model.ModuleDefinition;
51 import info.magnolia.objectfactory.Components;
52 import info.magnolia.resourceloader.Resource;
53 import info.magnolia.resourceloader.ResourceOrigin;
54 import info.magnolia.resourceloader.ResourceVisitor;
55 import info.magnolia.resourceloader.util.FileResourceCollectorVisitor;
56 import info.magnolia.resourceloader.util.PredicatedResourceVisitor;
57 import info.magnolia.resourceloader.util.VoidFunction;
58
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Map.Entry;
65 import java.util.Set;
66 import java.util.regex.Pattern;
67
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 import com.google.common.base.Optional;
72 import com.google.common.base.Predicate;
73 import com.google.common.collect.Collections2;
74 import com.google.common.collect.Maps;
75 import com.google.common.collect.Multimap;
76 import com.google.common.collect.MultimapBuilder;
77 import com.google.common.collect.Sets;
78
79
80
81
82
83
84 public abstract class AbstractFileResourceConfigurationSource<T> implements ConfigurationSource {
85
86 private static final Logger log = LoggerFactory.getLogger(AbstractFileResourceConfigurationSource.class);
87
88
89
90
91 private static final String PATH_PREFIX_TEMPLATE = "^%s($|/.+)";
92
93 private final ResourceOrigin<?> origin;
94 private final Pattern pathPattern;
95 private final ModuleRegistry moduleRegistry;
96 private final Registry<T> registry;
97 private final PathToMetadataInferrer pathToMetadataInferrer;
98 private final Set<FileDefinitionDecoratorResolver> definitionDecoratorResolvers = Sets.newHashSet();
99 private final Map<String, FileDefinitionDecorator<T>> resolvedDefinitionDecorators = Maps.newHashMap();
100
101
102
103
104 @Deprecated
105 public AbstractFileResourceConfigurationSource(ResourceOrigin<?> origin, Registry<T> registry, Pattern pathPattern) {
106 this(origin, registry, pathPattern, Components.getComponent(ModuleRegistry.class));
107 }
108
109
110
111
112 public AbstractFileResourceConfigurationSource(ResourceOrigin<?> origin, Registry<T> registry, Pattern pathPattern, ModuleRegistry moduleRegistry) {
113 this.origin = origin;
114 this.registry = registry;
115 this.pathPattern = pathPattern;
116 this.moduleRegistry = moduleRegistry;
117 this.pathToMetadataInferrer = new RegexBasedPathToMetadataInferrer(this.pathPattern);
118 }
119
120 @Override
121 public ConfigurationSourceType type() {
122 return ConfigurationSourceTypes.file;
123 }
124
125 @Override
126 public void start() {
127 log.info("Setting up {} to load {} definitions from resources", getClass().getSimpleName(), getRootType().getSimpleName());
128 final LoadAndRegisterFunction loadAndRegisterFunction = new LoadAndRegisterFunction();
129
130
131 final Multimap<String, Resource> definitionResources = MultimapBuilder.SetMultimapBuilder
132 .hashKeys()
133 .arrayListValues()
134 .build();
135 final DefinitionMetadataBuilder metadataBuilder = registry.newMetadataBuilder();
136 final FileResourceCollectorVisitor definitionProviderCollector = FileResourceCollectorVisitor.on(pathMatches(pathPattern));
137
138 origin.traverseWith(definitionProviderCollector);
139
140 definitionProviderCollector.getCollectedResources().forEach(resource -> {
141 pathToMetadataInferrer.populateFrom(metadataBuilder, resource);
142 definitionResources.put(metadataBuilder.getModule(), resource);
143 });
144
145 final List<String> moduleNameOrder = moduleRegistry
146 .getModuleDefinitions().stream()
147 .map(ModuleDefinition::getName)
148 .collect(toList());
149
150 definitionResources.entries().stream()
151 .sorted(comparing(r -> moduleNameOrder.indexOf(r.getKey())))
152 .map(Entry::getValue)
153 .forEach(loadAndRegisterFunction::apply);
154
155 origin.registerResourceChangeHandler(change -> {
156 switch (change.getType()) {
157 case MODIFIED:
158 case ADDED:
159
160
161
162
163 if (pathPattern.matcher(change.getRelatedResourcePath()).matches()) {
164 loadAndRegister(origin.getByPath(change.getRelatedResourcePath()));
165 }
166 break;
167 case REMOVED:
168
169
170
171
172 removeDefinitionsMatchingPath(change.getRelatedResourcePath());
173 break;
174 }
175 });
176
177 startDecoration();
178 }
179
180 protected void startDecoration() {
181 final VoidFunction<Resource> definitionDecoratorResolutionFunction = new DefinitionDecoratorResolutionFunction();
182 final ResourceVisitor definitionDecoratorResolver = PredicatedResourceVisitor.with(definitionDecoratorResolutionFunction);
183
184 origin.traverseWith(definitionDecoratorResolver);
185 origin.registerResourceChangeHandler(change -> {
186 switch (change.getType()) {
187 case MODIFIED:
188 case ADDED:
189 final Resource resource = origin.getByPath(change.getRelatedResourcePath());
190 definitionDecoratorResolutionFunction.apply(resource);
191 break;
192 case REMOVED:
193 removeDefinitionDecoratorsMatchingPath(change.getRelatedResourcePath());
194 break;
195 }
196 });
197 }
198
199 private void removeDefinitionDecoratorsMatchingPath(String removedResourcePath) {
200
201 final Pattern removedPathPattern = Pattern.compile(String.format(PATH_PREFIX_TEMPLATE, removedResourcePath));
202
203 final Iterator<Entry<String, FileDefinitionDecorator<T>>> it = resolvedDefinitionDecorators.entrySet().iterator();
204 while (it.hasNext()) {
205 final Entry<String, FileDefinitionDecorator<T>> decoratorMapping = it.next();
206 if (removedPathPattern.matcher(decoratorMapping.getKey()).matches()) {
207 registry.removeDecorator(decoratorMapping.getValue());
208 log.info("File based definition decorator removed from [{}] in [{}] registry", removedResourcePath, registry.type().getPluralName());
209 it.remove();
210 }
211 }
212 }
213
214 protected final void removeDefinitionsMatchingPath(String removedResourcePath) {
215
216 final Pattern removedPathPattern = Pattern.compile(String.format(PATH_PREFIX_TEMPLATE, removedResourcePath));
217
218
219
220
221
222
223
224
225 final Collection<DefinitionMetadata> idsToRemove = Collections2.filter(getRegistry().getAllMetadata(), new Predicate<DefinitionMetadata>() {
226 @Override
227 public boolean apply(DefinitionMetadata definitionMetadata) {
228 return removedPathPattern.matcher(definitionMetadata.getLocation()).matches();
229 }
230 });
231
232 if (!idsToRemove.isEmpty()) {
233 getRegistry().unregisterAndRegister(idsToRemove, Collections.<DefinitionProvider<T>>emptySet());
234 }
235 }
236
237 public abstract void loadAndRegister(Resource resource);
238
239 protected Registry<T> getRegistry() {
240 return registry;
241 }
242
243 protected final Class<T> getRootType() {
244 return registry.type().baseClass();
245 }
246
247 protected DefinitionMetadataBuilder createMetadata(Resource resource) {
248 final DefinitionMetadataBuilder metadataBuilder = registry.newMetadataBuilder()
249 .type(getRegistry().type())
250 .location(resource.getPath())
251 .configurationSourceType(ConfigurationSourceTypes.file);
252 return pathToMetadataInferrer.populateFrom(metadataBuilder, resource);
253 }
254
255
256 protected final void registerDefinitionDecoratorResolver(FileDefinitionDecoratorResolver resolver) {
257 definitionDecoratorResolvers.add(resolver);
258 }
259
260
261
262
263 protected final Map<String, FileDefinitionDecorator<T>> getResolvedDefinitionDecorators() {
264 return resolvedDefinitionDecorators;
265 }
266
267 private Optional<FileDefinitionDecorator<T>> resolveDecorator(Resource resource) {
268 for (final FileDefinitionDecoratorResolver resolver : definitionDecoratorResolvers) {
269 final Optional<FileDefinitionDecorator<T>> resolutionResult = resolver.resolve(resource);
270 if (resolutionResult.isPresent()) {
271 return resolutionResult;
272 }
273 }
274
275
276
277 resolvedDefinitionDecorators.remove(resource.getPath());
278
279 return Optional.absent();
280 }
281
282 private class LoadAndRegisterFunction extends VoidFunction<Resource> {
283 @Override
284 public void doWith(Resource resource) {
285 log.debug("Loading {} ({})", resource, resource.getPath());
286 loadAndRegister(resource);
287 }
288 }
289
290 private class DefinitionDecoratorResolutionFunction extends VoidFunction<Resource> {
291 @Override
292 public void doWith(Resource resource) {
293 final Optional<FileDefinitionDecorator<T>> resolvedDecorator = resolveDecorator(resource);
294 if (resolvedDecorator.isPresent()) {
295 final FileDefinitionDecorator<T> decorator = resolvedDecorator.get();
296 if (resolvedDefinitionDecorators.containsKey(resource.getPath())) {
297 log.info("File based definition decorator modified at [{}] and will be updated in [{}] registry", resource.getPath(), registry.type().getPluralName());
298 } else {
299 log.info("File based definition decorator resolved from [{}] and will be added to [{}] registry", resource.getPath(), registry.type().getPluralName());
300 }
301 resolvedDefinitionDecorators.put(resource.getPath(), decorator);
302 registry.addDecorator(decorator);
303 }
304 }
305 }
306 }