View Javadoc
1   /**
2    * This file Copyright (c) 2003-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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   * TODO : factor out into simpler units.
95   */
96  @Singleton
97  public class ModuleManagerImpl implements ModuleManager {
98  
99      private static final Logger log = LoggerFactory.getLogger(ModuleManagerImpl.class);
100 
101     // TODO : expose a method to retrieve a given module's node ?
102     // TODO : see InstallContextImpl.getOrCreateCurrentModuleConfigNode()
103     static final String MODULES_NODE = "modules";
104 
105     /**
106      * List<ModuleDefinition> of modules found to be deployed.
107      */
108     private List<ModuleDefinition> orderedModuleDescriptors;
109 
110     private ModuleManagementState state;
111 
112     // here we use the implementation, since it has extra methods that should not be exposed to Task methods.
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      * @deprecated since 5.4.7 - use {@link #ModuleManagerImpl(InstallContextImpl, Set, ModuleRegistry, DependencyChecker, Node2BeanProcessor, RepositoryManager)} instead.
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      * In addition to checking for install or updates, this method also loads
163      * repositories when there are no pending install or update tasks.
164      *
165      * @see info.magnolia.module.ModuleManager#checkForInstallOrUpdates()
166      */
167     @Override
168     public void checkForInstallOrUpdates() {
169         // compare and determine if we need to do anything
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         // TODO handle modules found in repo but not found on classpath
188 
189         installContext.setCurrentModule(null);
190         installContext.setTotalTaskCount(taskCount);
191 
192         // if we don't have to perform any update load repositories now
193         if (!state.needsUpdateOrInstall()) {
194             loadModulesRepositories();
195         }
196 
197         // TODO : check the force bootstrap properties
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; // TODO
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         // check all conditions
245         boolean conditionsChecked = true;
246         for (ModuleAndDeltas moduleAndDeltas : state.getList()) {
247             // TODO extract "do for all deltas" logic ?
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         // TODO : this isn't super clean.
280         final InstallStatus status = installContext.isRestartNeeded() ? InstallStatus.installDoneRestartNeeded : InstallStatus.installDone;
281         installContext.setStatus(status);
282 
283         // Exceptionally ask the system to do a garbage collection - installation requires a lot of memory that could be freed now.
284         // This method is only executed upon module installation, so no users should be affected.
285         System.gc();
286     }
287 
288     @Override
289     public InstallContext getInstallContext() {
290         return installContext;
291     }
292 
293     @Override
294     public void startModules() {
295         // process startup tasks before actually starting modules
296         executeStartupTasks();
297 
298         // here we use the implementation, since it has extra methods that should not be exposed to ModuleLifecycle methods.
299         // TODO we should keep only one instance of the lifecycle context
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                         // We use the currently set ComponentProvider which is main
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         // Acquire the system event bus, it lives in the 'main' component provider while ModuleManagerImpl lives in the
342         // 'platform' so we can't get it using IoC. Instead we use Components.getComponentProvider() which at this point
343         // returns 'main'.
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())); //FIXME kept for compatibility, still used for info.magnolia.module.admininterface.PageHandlerManager
359 
360         // Exceptionally ask the system to do a garbage collection - same as for installation, the startup consumes memory,
361         // which may prevent users to login after startup.
362         // This method is only executed upon startup, so no users should be affected.
363         System.gc();
364 
365         lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_MODULE_RESTART);
366     }
367 
368     /**
369      * Process startup tasks. Tasks retured by <code>ModuleDefinition.getStartupTasks()</code> are always executed and
370      * do not require manual intervention.
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      * Builds a map of properties to be set on the module instance, the properties are "moduleDefinition", "name",
404      * "moduleNode" and "configNode". This map is rebuilt every-time reloading takes place just in case the nodes have
405      * changed since startup. One situation where this is necessary is when a module does not have a config node at
406      * startup but one is added later on, see MAGNOLIA-3457.
407      *
408      * @deprecated since 6.1. The module instance should be created by info.magnolia.config.module.ModuleInstanceRegistry.
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      * @deprecated since 6.1. The module instance should be created by info.magnolia.config.module.ModuleInstanceRegistry.
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         // TODO we should keep only one instance of the lifecycle context
456         final ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
457         lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_SHUTDOWN);
458         if (orderedModuleDescriptors != null) {
459             // if module descriptors were read, let's shut down modules in reverse order
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      * Applies to given deltas for the given module. It is NOT responsible for setting the given
483      * module as being the current module in the given context, but it is responsible for unsetting
484      * it when done, and for saving upon success.
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             // TODO : ctx.info("Successful installation/update."); after save ?
508             ctx.setCurrentModule(null);
509         }
510 
511         saveChanges(success);
512     }
513 
514     /**
515      * Save changes to jcr, or revert them if something went wrong.
516      *
517      * @param persist if <code>true</code>, all workspaces are save; if <code>false</code> changes will be reverted.
518      */
519     private void saveChanges(boolean persist) {
520         // save all repositories once a module was properly installed/updated, or rollback changes.
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      * Initializes repositories and workspaces defined by modules.
558      * Perform repository registration tasks (create repositories or workspace, setup nodetypes) that should be done
559      * always before starting the new module.
560      */
561     private void loadModulesRepositories() {
562         for (ModuleDefinition def : orderedModuleDescriptors) {
563             // register repositories
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      * Loads a single repository plus its workspaces, register nodetypes and grant permissions to superuser.
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             // get the repository name from the mapping, users may want to manually add it here if needed
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             // override changed parameters
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             // register nodetypes
634             registerNodeTypeFile(repositoryName, nodeTypeFile);
635             // if this repo is not the default one, register nodetypes on default repo (MAGNOLIA-3189)
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                         // should never happen, the only exception we can get here is during login
650                         log.error(e.getMessage(), e);
651                     }
652                 }
653             }
654         }
655 
656     }
657 
658     /**
659      * Returns the repository definition for a repository or the repository for a given workspace.
660      */
661     // method copied 1-to-1 from former ContentRepository#getRepositoryMapping
662     // TODO: ditch the terminology mix-up between former repositories and workspaces for good
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      * Register nodeType file in repository.
673      *
674      * @param repositoryName repository name
675      * @param nodeTypeFile nodeType file
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 }