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