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