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.module.model.reader;
35
36 import info.magnolia.init.MagnoliaConfigurationProperties;
37 import info.magnolia.map2bean.Map2BeanTransformer;
38 import info.magnolia.module.InstallContext;
39 import info.magnolia.module.ModuleManagementException;
40 import info.magnolia.module.ModuleVersionHandler;
41 import info.magnolia.module.delta.Delta;
42 import info.magnolia.module.delta.DeltaBuilder;
43 import info.magnolia.module.delta.Task;
44 import info.magnolia.module.model.DependencyDefinition;
45 import info.magnolia.module.model.ModuleDefinition;
46 import info.magnolia.module.model.Version;
47
48 import java.io.BufferedReader;
49 import java.io.IOException;
50 import java.io.InputStreamReader;
51 import java.io.Reader;
52 import java.nio.charset.StandardCharsets;
53 import java.nio.file.FileVisitOption;
54 import java.nio.file.FileVisitResult;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.nio.file.SimpleFileVisitor;
59 import java.nio.file.attribute.BasicFileAttributes;
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.regex.Matcher;
66 import java.util.regex.Pattern;
67
68 import javax.inject.Inject;
69 import javax.inject.Singleton;
70
71 import org.apache.commons.lang3.StringUtils;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74 import org.yaml.snakeyaml.Yaml;
75
76 import com.google.common.collect.ImmutableList;
77 import com.google.common.collect.Lists;
78 import com.google.common.collect.Maps;
79
80
81
82
83
84
85
86
87
88
89
90 @Singleton
91 public class LightModuleDefinitionReader implements ModuleDefinitionReader {
92
93 private static final Logger log = LoggerFactory.getLogger(LightModuleDefinitionReader.class);
94
95 private static final String LIGHT_MODULE_DESCRIPTOR_NAME = "module.yaml";
96 private static final String MAGNOLIA_HOME = "magnolia.home";
97 private static final Pattern LIGHT_MODULE_DESCRIPTOR_PATH_PATTERN = Pattern.compile(String.format("^[\\w|/]*/(?<parent>\\w+?)/%s$", LIGHT_MODULE_DESCRIPTOR_NAME));
98
99 private static final String RESOURCES_DIR_PROPERTY = "magnolia.resources.dir";
100 private final MagnoliaConfigurationProperties configurationProperties;
101 private final Map2BeanTransformer map2BeanTransformer;
102
103 @Inject
104 public LightModuleDefinitionReader(MagnoliaConfigurationProperties configurationProperties, Map2BeanTransformer map2BeanTransformer) {
105 this.configurationProperties = configurationProperties;
106 this.map2BeanTransformer = map2BeanTransformer;
107 }
108
109 @Override
110 public Map<String, ModuleDefinition> readAll() throws ModuleManagementException {
111 final Map<String, ModuleDefinition> moduleDefinitions = Maps.newHashMap();
112 final Collection<Path> lightModuleDescriptors;
113
114 try {
115 lightModuleDescriptors = collectLightModuleDescriptorFiles();
116 } catch (IOException e) {
117 throw new ModuleManagementException("Encountered I/O issue during light module descriptors collection", e);
118 }
119
120 for (final Path lightModuleDescriptor : lightModuleDescriptors) {
121 try (final Reader reader = Files.newBufferedReader(lightModuleDescriptor, StandardCharsets.UTF_8)) {
122 final ModuleDefinition definition = read(reader, lightModuleDescriptor.getParent().getFileName().toString());
123 moduleDefinitions.put(definition.getName(), definition);
124 } catch (Exception e) {
125 throw new ModuleManagementException(String.format("Failed to read a light module descriptor at [%s]", lightModuleDescriptor), e);
126 }
127 }
128
129 return moduleDefinitions;
130 }
131
132 @Override
133 public ModuleDefinition read(Reader in) throws ModuleManagementException {
134 final ModuleDefinition moduleDefinition = read(in, "");
135 if (StringUtils.isBlank(moduleDefinition.getName())) {
136 throw new ModuleManagementException("Attempted to resolve light module definition via LightModuleDefinitionReader.read(java.io.Reader) without module name specified explicitly in the module descriptor file");
137 }
138 return moduleDefinition;
139 }
140
141 @Override
142 public ModuleDefinition readFromResource(String resourcePath) throws ModuleManagementException {
143 final Matcher lightModuleDescriptorPathMatcher = LIGHT_MODULE_DESCRIPTOR_PATH_PATTERN.matcher(resourcePath);
144 if (lightModuleDescriptorPathMatcher.matches()) {
145 try (Reader reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(resourcePath)))) {
146 return read(reader, lightModuleDescriptorPathMatcher.group("parent"));
147 } catch (IOException e) {
148 throw new ModuleManagementException(String.format("Failed to read a light module definition from the [%s]", resourcePath), e);
149 }
150 } else {
151 throw new ModuleManagementException(String.format("[%s] does not match a light module descriptor path pattern", resourcePath));
152 }
153 }
154
155 private ModuleDefinition read(final Reader in, String nameFallback) {
156 final Object yaml = new Yaml().load(in);
157 if (!(yaml instanceof Map)) {
158 throw new IllegalArgumentException("Provided YAML stream does not yield a map");
159 }
160
161 @SuppressWarnings("unchecked") Map<String, Object> yamlData = (Map<String, Object>) yaml;
162 try {
163 final LightModuleDefinition lightModuleDefinition = map2BeanTransformer.toBean(yamlData, LightModuleDefinition.class);
164 final ModuleDefinition moduleDefinition = new ModuleDefinition();
165
166 String name = lightModuleDefinition.getName();
167 if (StringUtils.isBlank(name)) {
168 name = nameFallback;
169 }
170
171 moduleDefinition.setVersion(lightModuleDefinition.getVersion());
172 moduleDefinition.setDependencies(ImmutableList.<DependencyDefinition>copyOf(lightModuleDefinition.getDependencies()));
173 moduleDefinition.setName(name);
174 moduleDefinition.setVersionHandler(LightModuleVersionHandler.class);
175
176 return moduleDefinition;
177 } catch (Exception e) {
178 throw new RuntimeException("Failed to parse light module definition from YAML content stream", e);
179 }
180 }
181
182 private Collection<Path> collectLightModuleDescriptorFiles() throws IOException {
183 final List<Path> lightModuleDescriptorPaths = Lists.newLinkedList();
184
185 String resourcesDir = configurationProperties.getProperty(RESOURCES_DIR_PROPERTY);
186 if (resourcesDir == null) {
187 resourcesDir = configurationProperties.getProperty(MAGNOLIA_HOME);
188 log.warn("Could not find '{}' property, please update your 'magnolia.properties'. Falling back to '{}': '{}'.", RESOURCES_DIR_PROPERTY, MAGNOLIA_HOME, resourcesDir);
189 }
190
191 final Path mgnlResourceDirectory = Paths.get(resourcesDir);
192
193 Files.walkFileTree(mgnlResourceDirectory, Collections.<FileVisitOption>emptySet(), 2, new SimpleFileVisitor<Path>() {
194
195 @Override
196 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
197 log.debug("Looking for [{}] within [{}]", LIGHT_MODULE_DESCRIPTOR_NAME, dir.getFileName());
198 return super.preVisitDirectory(dir, attrs);
199 }
200
201 @Override
202 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
203 if (LIGHT_MODULE_DESCRIPTOR_NAME.equals(file.getFileName().toString())) {
204 log.debug("Encountered [{}] file inside of [{}]", LIGHT_MODULE_DESCRIPTOR_NAME, file.getParent().getFileName());
205 lightModuleDescriptorPaths.add(file);
206 return FileVisitResult.SKIP_SIBLINGS;
207 }
208 return FileVisitResult.CONTINUE;
209 }
210 });
211
212 return lightModuleDescriptorPaths;
213 }
214
215
216
217
218
219
220
221
222 public static class LightModuleDependencyDefinition extends DependencyDefinition {
223 }
224
225
226
227
228
229
230 public static class LightModuleDefinition {
231
232 private String name;
233
234 private Version version;
235
236 private Collection<LightModuleDependencyDefinition> dependencies = new ArrayList<>();
237
238 public Version getVersion() {
239 return version;
240 }
241
242 public void setVersion(Version version) {
243 this.version = version;
244 }
245
246 public Collection<LightModuleDependencyDefinition> getDependencies() {
247 return dependencies;
248 }
249
250 public void setDependencies(Collection<LightModuleDependencyDefinition> dependencies) {
251 this.dependencies = dependencies;
252 }
253
254 public String getName() {
255 return name;
256 }
257
258 public void setName(String name) {
259 this.name = name;
260 }
261 }
262
263
264
265
266
267 public static final class LightModuleVersionHandler implements ModuleVersionHandler {
268
269 @Override
270 public Version getCurrentlyInstalled(InstallContext ctx) {
271 return ctx.getCurrentModuleDefinition().getVersion();
272 }
273
274 @Override
275 public List<Delta> getDeltas(InstallContext installContext, Version from) {
276 return Collections.emptyList();
277 }
278
279 @Override
280 public Delta getStartupDelta(InstallContext ctx) {
281 return DeltaBuilder.startup(ctx.getCurrentModuleDefinition(), Collections.<Task>emptyList());
282 }
283 }
284 }