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;
35
36 import info.magnolia.cms.beans.config.ContentRepository;
37 import info.magnolia.cms.core.Content;
38 import info.magnolia.cms.core.HierarchyManager;
39 import info.magnolia.cms.core.SystemProperty;
40 import info.magnolia.cms.util.ObservationUtil;
41 import info.magnolia.cms.util.SystemContentWrapper;
42 import info.magnolia.content2bean.Content2BeanException;
43 import info.magnolia.content2bean.Content2BeanUtil;
44 import info.magnolia.context.MgnlContext;
45 import info.magnolia.module.delta.Condition;
46 import info.magnolia.module.delta.Delta;
47 import info.magnolia.module.delta.Task;
48 import info.magnolia.module.delta.TaskExecutionException;
49 import info.magnolia.module.model.ModuleDefinition;
50 import info.magnolia.module.model.RepositoryDefinition;
51 import info.magnolia.module.model.Version;
52 import info.magnolia.module.model.reader.BetwixtModuleDefinitionReader;
53 import info.magnolia.module.model.reader.DependencyChecker;
54 import info.magnolia.module.model.reader.DependencyCheckerImpl;
55 import info.magnolia.module.model.reader.ModuleDefinitionReader;
56 import info.magnolia.module.ui.ModuleManagerNullUI;
57 import info.magnolia.module.ui.ModuleManagerUI;
58 import info.magnolia.module.ui.ModuleManagerWebUI;
59 import info.magnolia.objectfactory.ClassFactory;
60 import info.magnolia.objectfactory.Classes;
61 import info.magnolia.objectfactory.Components;
62 import info.magnolia.objectfactory.MgnlInstantiationException;
63 import info.magnolia.repository.Provider;
64 import info.magnolia.repository.RepositoryConstants;
65
66 import java.util.ArrayList;
67 import java.util.Collection;
68 import java.util.Collections;
69 import java.util.HashMap;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Map;
73
74 import javax.inject.Inject;
75 import javax.inject.Singleton;
76 import javax.jcr.RepositoryException;
77 import javax.jcr.observation.EventIterator;
78 import javax.jcr.observation.EventListener;
79
80 import org.apache.commons.beanutils.BeanUtils;
81 import org.apache.commons.lang.StringUtils;
82 import org.apache.commons.lang.exception.ExceptionUtils;
83
84
85
86
87
88
89
90 @Singleton
91 public class ModuleManagerImpl implements ModuleManager {
92
93 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ModuleManagerImpl.class);
94
95 private static final int DEFAULT_MODULE_OBSERVATION_DELAY = 5000;
96 private static final int DEFAULT_MODULE_OBSERVATION_MAX_DELAY = 30000;
97
98
99
100 static final String MODULES_NODE = "modules";
101
102
103
104
105 private List<ModuleDefinition> orderedModuleDescriptors;
106
107 private ModuleManagementState state;
108
109
110 private final InstallContextImpl installContext;
111
112 private final ModuleRegistry registry;
113 private final ModuleDefinitionReader moduleDefinitionReader;
114 private final DependencyChecker dependencyChecker;
115
116
117
118
119 protected ModuleManagerImpl() {
120 this(new InstallContextImpl(ModuleRegistry.Factory.getInstance()), new BetwixtModuleDefinitionReader());
121 }
122
123
124
125
126 protected ModuleManagerImpl(InstallContextImpl installContext, ModuleDefinitionReader moduleDefinitionReader) {
127 this(installContext, moduleDefinitionReader, ModuleRegistry.Factory.getInstance(), new DependencyCheckerImpl());
128 }
129
130 @Inject
131 public ModuleManagerImpl(InstallContextImpl installContext, ModuleDefinitionReader moduleDefinitionReader, ModuleRegistry moduleRegistry, DependencyChecker dependencyChecker) {
132 this.installContext = installContext;
133 this.moduleDefinitionReader = moduleDefinitionReader;
134 this.registry = moduleRegistry;
135 this.dependencyChecker = dependencyChecker;
136 }
137
138 @Override
139 public List<ModuleDefinition> loadDefinitions() throws ModuleManagementException {
140 if (state != null) {
141 throw new IllegalStateException("ModuleManager was already initialized !");
142 }
143
144 final Map<String, ModuleDefinition> moduleDefinitions = moduleDefinitionReader.readAll();
145 if (moduleDefinitions.isEmpty()) {
146 throw new ModuleManagementException("No module definition was found.");
147 }
148 log.debug("Loaded definitions: {}", moduleDefinitions);
149
150 dependencyChecker.checkDependencies(moduleDefinitions);
151 orderedModuleDescriptors = dependencyChecker.sortByDependencyLevel(moduleDefinitions);
152 for (ModuleDefinition moduleDefinition : orderedModuleDescriptors) {
153 registry.registerModuleDefinition(moduleDefinition.getName(), moduleDefinition);
154 }
155 return orderedModuleDescriptors;
156 }
157
158
159
160
161
162
163
164 @Override
165 public void checkForInstallOrUpdates() {
166
167 state = new ModuleManagementState();
168 int taskCount = 0;
169 for (ModuleDefinition module : orderedModuleDescriptors) {
170 installContext.setCurrentModule(module);
171 log.debug("Checking for installation or update [{}]", module);
172 final ModuleVersionHandler versionHandler = newVersionHandler(module);
173 registry.registerModuleVersionHandler(module.getName(), versionHandler);
174
175 final Version currentVersion = versionHandler.getCurrentlyInstalled(installContext);
176 final List<Delta> deltas = versionHandler.getDeltas(installContext, currentVersion);
177 if (deltas.size() > 0) {
178 state.addModule(module, currentVersion, deltas);
179 for (Delta delta : deltas) {
180 taskCount += delta.getTasks().size();
181 }
182 }
183 }
184
185
186 installContext.setCurrentModule(null);
187 installContext.setTotalTaskCount(taskCount);
188
189
190 if (!state.needsUpdateOrInstall()) {
191 loadModulesRepositories();
192 }
193
194
195 }
196
197 @Override
198 public ModuleManagementState getStatus() {
199 if (state == null) {
200 throw new IllegalStateException("ModuleManager was not initialized !");
201 }
202
203 return state;
204 }
205
206 @Override
207 public ModuleManagerUI getUI() {
208 if (SystemProperty.getBooleanProperty("magnolia.update.auto")) {
209 return new ModuleManagerNullUI(this);
210 }
211 return new ModuleManagerWebUI(this);
212 }
213
214 protected ModuleVersionHandler newVersionHandler(ModuleDefinition module) {
215 try {
216 final Class<? extends ModuleVersionHandler> versionHandlerClass = module.getVersionHandler();
217 if (versionHandlerClass != null) {
218 return Classes.getClassFactory().newInstance(versionHandlerClass);
219 }
220 return new DefaultModuleVersionHandler();
221 } catch (MgnlInstantiationException e) {
222 throw e;
223 }
224 }
225
226 @Override
227 public void performInstallOrUpdate() {
228 synchronized (installContext) {
229 if (state == null) {
230 throw new IllegalStateException("ModuleManager was not initialized !");
231 }
232 if (!state.needsUpdateOrInstall()) {
233 throw new IllegalStateException("ModuleManager has nothing to do !");
234 }
235 if (installContext.getStatus() != null) {
236 throw new IllegalStateException("ModuleManager.performInstallOrUpdate() was already started !");
237 }
238 installContext.setStatus(InstallStatus.inProgress);
239 }
240
241
242 boolean conditionsChecked = true;
243 for (ModuleAndDeltas moduleAndDeltas : state.getList()) {
244
245 installContext.setCurrentModule(moduleAndDeltas.getModule());
246 for (Delta delta : moduleAndDeltas.getDeltas()) {
247 final List<Condition> conditions = delta.getConditions();
248 for (Condition cond : conditions) {
249 if (!cond.check(installContext)) {
250 conditionsChecked = false;
251 installContext.warn(cond.getDescription());
252 }
253 }
254 }
255 }
256 installContext.setCurrentModule(null);
257 if (!conditionsChecked) {
258 installContext.setStatus(InstallStatus.stoppedConditionsNotMet);
259 return;
260 }
261
262 loadModulesRepositories();
263
264 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
265 @Override
266 public void doExec() {
267 final Iterator<ModuleAndDeltas> it = state.getList().iterator();
268 while (it.hasNext()) {
269 final ModuleAndDeltas moduleAndDeltas = it.next();
270 installOrUpdateModule(moduleAndDeltas, installContext);
271 it.remove();
272 }
273 }
274 }, true);
275
276
277 final InstallStatus status = installContext.isRestartNeeded() ? InstallStatus.installDoneRestartNeeded : InstallStatus.installDone;
278 installContext.setStatus(status);
279 }
280
281 @Override
282 public InstallContext getInstallContext() {
283 return installContext;
284 }
285
286 @Override
287 public void startModules() {
288
289 executeStartupTasks();
290
291
292
293 final ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
294 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_STARTUP);
295 final HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
296 Content modulesParentNode;
297 try {
298 modulesParentNode = hm.getContent(MODULES_NODE);
299 }
300 catch (RepositoryException e) {
301 throw new RuntimeException("Can't start module due to failing to load the /modules node.",e);
302 }
303 final Collection<Content> moduleNodes = new ArrayList<Content>();
304
305 for (ModuleDefinition moduleDefinition : orderedModuleDescriptors) {
306 final String moduleClassName = moduleDefinition.getClassName();
307 final String moduleName = moduleDefinition.getName();
308 log.info("Initializing module {}", moduleName);
309
310 try {
311 final Object moduleInstance;
312 if (moduleClassName != null) {
313 try {
314 final ClassFactory classFactory = Classes.getClassFactory();
315 final Class<?> moduleClass = classFactory.forName(moduleClassName);
316
317 moduleInstance = Components.newInstance(moduleClass);
318 } catch (Throwable t) {
319 log.error("Can't instantiate " + moduleClassName + " for module " + moduleName + " : " + t.getClass() + " : " + t.getMessage(), t);
320 continue;
321 }
322
323 registry.registerModuleInstance(moduleName, moduleInstance);
324 } else {
325 moduleInstance = null;
326 }
327
328 if (modulesParentNode.hasContent(moduleName)) {
329 moduleNodes.add(new SystemContentWrapper(modulesParentNode.getContent(moduleName)));
330 }
331
332 if (moduleInstance != null) {
333
334 populateModuleInstance(moduleInstance, getModuleInstanceProperties(moduleDefinition));
335
336 startModule(moduleInstance, moduleDefinition, lifecycleContext);
337
338
339 ObservationUtil.registerDeferredChangeListener(RepositoryConstants.CONFIG, "/modules/" + moduleName + "/config", new EventListener() {
340
341 @Override
342 public void onEvent(EventIterator events) {
343 final Object moduleInstance = registry.getModuleInstance(moduleName);
344 final ModuleDefinition moduleDefinition = registry.getDefinition(moduleName);
345
346
347 final ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
348 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_MODULE_RESTART);
349 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
350 @Override
351 public void doExec() {
352 stopModule(moduleInstance, moduleDefinition, lifecycleContext);
353 populateModuleInstance(moduleInstance, getModuleInstanceProperties(moduleDefinition));
354 startModule(moduleInstance, moduleDefinition, lifecycleContext);
355 }
356 }, true);
357 }
358 }, DEFAULT_MODULE_OBSERVATION_DELAY, DEFAULT_MODULE_OBSERVATION_MAX_DELAY);
359 }
360 }
361 catch (Throwable th) {
362 log.error("Can't start module " + moduleName, th);
363 }
364 }
365
366 lifecycleContext.start(moduleNodes);
367 }
368
369
370
371
372
373 protected void executeStartupTasks() {
374 MgnlContext.doInSystemContext(new MgnlContext.VoidOp() {
375 @Override
376 public void doExec() {
377 for (ModuleDefinition module : orderedModuleDescriptors) {
378 final ModuleVersionHandler versionHandler = registry.getVersionHandler(module.getName());
379 installContext.setCurrentModule(module);
380 final Delta startup = versionHandler.getStartupDelta(installContext);
381 applyDeltas(module, Collections.singletonList(startup), installContext);
382 }
383 }
384 }, false);
385 }
386
387 protected void startModule(Object moduleInstance, final ModuleDefinition moduleDefinition, final ModuleLifecycleContextImpl lifecycleContext) {
388 if (moduleInstance instanceof ModuleLifecycle) {
389 lifecycleContext.setCurrentModuleDefinition(moduleDefinition);
390 log.info("Starting module {}", moduleDefinition.getName());
391 ((ModuleLifecycle) moduleInstance).start(lifecycleContext);
392 }
393 }
394
395 protected void stopModule(Object moduleInstance, final ModuleDefinition moduleDefinition, final ModuleLifecycleContextImpl lifecycleContext) {
396 if (moduleInstance instanceof ModuleLifecycle) {
397 lifecycleContext.setCurrentModuleDefinition(moduleDefinition);
398 log.info("Stopping module {}", moduleDefinition.getName());
399 ((ModuleLifecycle) moduleInstance).stop(lifecycleContext);
400 }
401 }
402
403
404
405
406
407
408
409 protected Map<String, Object> getModuleInstanceProperties(ModuleDefinition moduleDefinition) {
410
411 final HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
412
413 final String moduleNodePath = "/modules/" + moduleDefinition.getName();
414 Content moduleNode = null;
415 try {
416 moduleNode = hm.isExist(moduleNodePath) ? new SystemContentWrapper(hm.getContent(moduleNodePath)) : null;
417 } catch (RepositoryException e) {
418 log.error("Wasn't able to acquire module node " + moduleNodePath + ": " + e.getMessage(), e);
419 }
420
421 final String moduleConfigPath = "/modules/" + moduleDefinition.getName() + "/config";
422 Content configNode = null;
423 try {
424 configNode = hm.isExist(moduleConfigPath) ? new SystemContentWrapper(hm.getContent(moduleConfigPath)) : null;
425 } catch (RepositoryException e) {
426 log.error("Wasn't able to acquire module config node " + moduleConfigPath + ": " + e.getMessage(), e);
427 }
428
429 final Map<String, Object> moduleProperties = new HashMap<String, Object>();
430 moduleProperties.put("moduleDefinition", moduleDefinition);
431 moduleProperties.put("name", moduleDefinition.getName());
432 moduleProperties.put("moduleNode", moduleNode);
433 moduleProperties.put("configNode", configNode);
434
435 return moduleProperties;
436 }
437
438 protected void populateModuleInstance(Object moduleInstance, Map<String, Object> moduleProperties) {
439
440 try {
441 BeanUtils.populate(moduleInstance, moduleProperties);
442 }
443 catch (Throwable e) {
444 log.error("Can't initialize module " + moduleInstance + ": " + e.getMessage(), e);
445 }
446
447 if (moduleProperties.get("configNode") != null) {
448 try {
449 Content2BeanUtil.setProperties(moduleInstance, (Content) moduleProperties.get("configNode"), true);
450 }
451 catch (Content2BeanException e) {
452 log.error("Wasn't able to configure module " + moduleInstance + ": " + e.getMessage(), e);
453 }
454 }
455 }
456
457 @Override
458 public void stopModules() {
459
460 final ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
461 lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_SHUTDOWN);
462 if (orderedModuleDescriptors != null) {
463
464 final ArrayList<ModuleDefinition> shutdownOrder = new ArrayList<ModuleDefinition>(orderedModuleDescriptors);
465 Collections.reverse(shutdownOrder);
466 for (ModuleDefinition md : shutdownOrder) {
467 Object module = registry.getModuleInstance(md.getName());
468 if (module instanceof ModuleLifecycle) {
469 stopModule(module, md, lifecycleContext);
470 }
471
472 }
473 }
474 }
475
476 protected void installOrUpdateModule(ModuleAndDeltas moduleAndDeltas, InstallContextImpl ctx) {
477 final ModuleDefinition moduleDef = moduleAndDeltas.getModule();
478 final List<Delta> deltas = moduleAndDeltas.getDeltas();
479 ctx.setCurrentModule(moduleDef);
480 log.debug("Install/update for {} is starting: {}", moduleDef, moduleAndDeltas);
481 applyDeltas(moduleDef, deltas, ctx);
482 log.debug("Install/update for {} has finished", moduleDef, moduleAndDeltas);
483 }
484
485
486
487
488
489
490 protected void applyDeltas(ModuleDefinition moduleDef, List<Delta> deltas, InstallContextImpl ctx) {
491 boolean success = true;
492 Task currentTask = null;
493 try {
494 for (Delta delta : deltas) {
495 final List<Task> tasks = delta.getTasks();
496 for (Task task : tasks) {
497 currentTask = task;
498 log.debug("Module {}, executing {}", moduleDef, currentTask);
499 task.execute(ctx);
500 ctx.incExecutedTaskCount();
501 }
502 }
503 } catch (TaskExecutionException e) {
504 ctx.error("Could not install or update " + moduleDef.getName() + " module. Task '" + currentTask.getName() + "' failed. (" + ExceptionUtils.getRootCauseMessage(e) + ")", e);
505 success = false;
506 } catch (RuntimeException e) {
507 ctx.error("Error while installing or updating " + moduleDef.getName() + " module. Task '" + currentTask.getName() + "' failed. (" + ExceptionUtils.getRootCauseMessage(e) + ")", e);
508 throw e;
509 } finally {
510
511 ctx.setCurrentModule(null);
512 }
513
514 saveChanges(success);
515 }
516
517
518
519
520
521 private void saveChanges(boolean persist) {
522
523 final Iterator<String> reposIt = ContentRepository.getAllRepositoryNames();
524 while (reposIt.hasNext()) {
525 final String repoName = reposIt.next();
526 log.debug((persist ? "Saving" : "Rolling back") + " repository " + repoName);
527 final HierarchyManager hm = MgnlContext.getHierarchyManager(repoName);
528 try {
529
530 if (hm.getWorkspace().getSession().hasPendingChanges()) {
531 if (persist) {
532 hm.save();
533 }
534 else {
535 hm.refresh(false);
536 }
537 }
538 }
539 catch (RepositoryException e) {
540 throw new RuntimeException(e);
541 }
542 }
543 }
544
545
546
547
548
549
550 private void loadModulesRepositories() {
551 for (ModuleDefinition def : orderedModuleDescriptors) {
552
553 for (final RepositoryDefinition repDef : def.getRepositories()) {
554 final String repositoryName = repDef.getName();
555
556 final String nodetypeFile = repDef.getNodeTypeFile();
557
558 final List<String> wsList = repDef.getWorkspaces();
559 String[] workSpaces = wsList.toArray(new String[wsList.size()]);
560
561 loadRepository(repositoryName, nodetypeFile, workSpaces);
562 }
563 }
564 }
565
566
567
568
569 private void loadRepository(String repositoryNameFromModuleDescriptor, String nodeTypeFile, String[] workspaces) {
570
571 if (workspaces == null || workspaces.length == 0)
572 {
573 log.error("Trying to register the repository {} without any workspace.", repositoryNameFromModuleDescriptor);
574 return;
575 }
576
577 final String DEFAULT_REPOSITORY_NAME = "magnolia";
578 String repositoryName = repositoryNameFromModuleDescriptor;
579
580 if (workspaces.length > 0) {
581
582 info.magnolia.repository.definition.RepositoryDefinition repositoryMapping = ContentRepository.getRepositoryMapping(workspaces[0]);
583 if (repositoryMapping != null) {
584 repositoryName = repositoryMapping.getName();
585 }
586 }
587
588 info.magnolia.repository.definition.RepositoryDefinition rm = ContentRepository.getRepositoryMapping(repositoryName);
589
590 if (rm == null) {
591
592 final info.magnolia.repository.definition.RepositoryDefinition defaultRepositoryMapping = ContentRepository.getRepositoryMapping(DEFAULT_REPOSITORY_NAME);
593 final Map<String, String> defaultParameters = defaultRepositoryMapping.getParameters();
594
595 rm = new info.magnolia.repository.definition.RepositoryDefinition();
596 rm.setName(repositoryName);
597 rm.setProvider(defaultRepositoryMapping.getProvider());
598 rm.setLoadOnStartup(true);
599
600 final Map<String, String> parameters = new HashMap<String, String>();
601 parameters.putAll(defaultParameters);
602
603
604 final String bindName = repositoryName + StringUtils.replace(defaultParameters.get("bindName"), "magnolia", "");
605 final String repositoryHome = StringUtils.substringBeforeLast(defaultParameters.get("configFile"), "/")
606 + "/"
607 + repositoryName;
608
609 parameters.put("repositoryHome", repositoryHome);
610 parameters.put("bindName", bindName);
611 parameters.put("customNodeTypes", nodeTypeFile);
612
613 rm.setParameters(parameters);
614
615 try {
616 ContentRepository.loadRepository(rm);
617 } catch (Exception e) {
618 log.error(e.getMessage(), e);
619 }
620 }
621
622 if (nodeTypeFile != null) {
623
624 registerNodeTypeFile(repositoryName, nodeTypeFile);
625
626 if (!DEFAULT_REPOSITORY_NAME.equals(repositoryName)) {
627 registerNodeTypeFile(DEFAULT_REPOSITORY_NAME, nodeTypeFile);
628 }
629 }
630
631 if (workspaces != null) {
632 for (String workspace : workspaces) {
633 if (!rm.getWorkspaces().contains(workspace)) {
634 log.debug("Loading new workspace: {}", workspace);
635
636 try {
637 ContentRepository.loadWorkspace(repositoryName, workspace);
638 }
639 catch (RepositoryException e) {
640
641 log.error(e.getMessage(), e);
642 }
643 }
644 }
645 }
646
647 }
648
649
650
651
652
653
654 private void registerNodeTypeFile(String repositoryName, String nodeTypeFile) {
655 Provider provider = ContentRepository.getRepositoryProvider(repositoryName);
656 try {
657 provider.registerNodeTypes(nodeTypeFile);
658 }
659 catch (RepositoryException e) {
660 log.error(e.getMessage(), e);
661 }
662 }
663 }