View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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.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   * TODO : factor out into simpler units.
89   *
90   * @author gjoseph
91   * @version $Revision: $ ($Author: $)
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     // 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 
115     private final ModuleRegistry registry;
116     private final ModuleDefinitionReader moduleDefinitionReader;
117     private final DependencyChecker dependencyChecker;
118     private final Node2BeanProcessor nodeToBean;
119 
120     /**
121      * @deprecated since 4.5 - use IoC - temporarily kept for tests ?
122      */
123     @Deprecated
124     protected ModuleManagerImpl() {
125         this(new InstallContextImpl(ModuleRegistry.Factory.getInstance()), new BetwixtModuleDefinitionReader());
126     }
127 
128     /**
129      * @deprecated since 4.5 - use IoC - temporarily kept for tests ?
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      * In addition to checking for install or updates, this method also loads
167      * repositories when there are no pending install or update tasks.
168      *
169      * @see info.magnolia.module.ModuleManager#checkForInstallOrUpdates()
170      */
171     @Override
172     public void checkForInstallOrUpdates() {
173         // compare and determine if we need to do anything
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         // TODO handle modules found in repo but not found on classpath
192 
193         installContext.setCurrentModule(null);
194         installContext.setTotalTaskCount(taskCount);
195 
196         // if we don't have to perform any update load repositories now
197         if (!state.needsUpdateOrInstall()) {
198             loadModulesRepositories();
199         }
200 
201         // TODO : check the force bootstrap properties
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; // TODO
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         // check all conditions
249         boolean conditionsChecked = true;
250         for (ModuleAndDeltas moduleAndDeltas : state.getList()) {
251             // TODO extract "do for all deltas" logic ?
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         // TODO : this isn't super clean.
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         // 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 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                         // We use the currently set ComponentProvider which is main
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                     // Still registering module instances with ModuleRegistry, although now one should use IoC and *depend* on such objects instead.
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                     // start observation
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                             // TODO we should keep only one instance of the lifecycle context
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      * Process startup tasks. Tasks retured by <code>ModuleDefinition.getStartupTasks()</code> are always executed and
378      * do not require manual intervention.
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      * Builds a map of properties to be set on the module instance, the properties are "moduleDefinition", "name",
412      * "moduleNode" and "configNode". This map is rebuilt every-time reloading takes place just in case the nodes have
413      * changed since startup. One situation where this is necessary is when a module does not have a config node at
414      * startup but one is added later on, see MAGNOLIA-3457.
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         // TODO we should keep only one instance of the lifecycle context
469         final ModuleLifecycleContextImpl lifecycleContext = new ModuleLifecycleContextImpl();
470         lifecycleContext.setPhase(ModuleLifecycleContext.PHASE_SYSTEM_SHUTDOWN);
471         if (orderedModuleDescriptors != null) {
472             // if module descriptors were read, let's shut down modules in reverse order
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      * Applies to given deltas for the given module. It is NOT responsible for setting the given
496      * module as being the current module in the given context, but it is responsible for unsetting
497      * it when done, and for saving upon success.
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             // TODO : ctx.info("Successful installation/update."); after save ?
520             ctx.setCurrentModule(null);
521         }
522 
523         saveChanges(success);
524     }
525 
526     /**
527      * Save changes to jcr, or revert them if something went wrong.
528      * @param persist if <code>true</code>, all workspaces are save; if <code>false</code> changes will be reverted.
529      */
530     private void saveChanges(boolean persist) {
531         // save all repositories once a module was properly installed/updated, or rollback changes.
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                 // don't call save or refresh if useless
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); // TODO
550             }
551         }
552     }
553 
554     /**
555      * Initializes repositories and workspaces defined by modules.
556      * Perform repository registration tasks (create repositories or workspace, setup nodetypes) that should be done
557      * always before starting the new module.
558      */
559     private void loadModulesRepositories() {
560         for (ModuleDefinition def : orderedModuleDescriptors) {
561             // register repositories
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      * Loads a single repository plus its workspaces, register nodetypes and grant permissions to superuser.
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             // get the repository name from the mapping, users may want to manually add it here if needed
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             // override changed parameters
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             // register nodetypes
633             registerNodeTypeFile(repositoryName, nodeTypeFile);
634             // if this repo is not the default one, register nodetypes on default repo (MAGNOLIA-3189)
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                         // 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      * Register nodeType file in repository.
660      * @param repositoryName repository name
661      * @param nodeTypeFile nodeType file
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 }