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