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