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