Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Prior concept Concept Module downloader updater 

Referenced implementation External module management support

Related concept Components in Magnolia  # Magnolia Startup and Shutdown sequence

Table of Contents

Introduction

...

The configuration point for this is within 'magnolia.properties' file with below default value:

magnolia.config.storeLocation=${magnolia.home}/WEB-INF/config/default/magnolia-config.properties


Start / stop module

With traditional Mangolia modules, we didn't really put our attentions to Module lifecycle management which means we didn't really implemented module 'start' and 'stop' function. However it is fully supported and in some cases, customers might want to implement them to support them persist of their critical information. Currently the start and stop just call corresponding functions of the modules.

Remember to store persistence config when module manager stop all modules before system shutdown.

Now let's go the difficult part - change module manager to support installing a new module on the fly.

...

To implement this, we need to break out and refactor module manager in below detail. Example file for reference: ModuleManagerImplExt.java

External Jar file loader support

This function get into consideration when we support dynamic loading of new jars file, instead of putting them within WEB-INF/lib so that any Application server such as Tomcat can load it automatically, we have to support dynamic jar file loading where-ever the file is located. Eventhough hacking directly to URLClassLoader is not recommended and will be deprecated soon, but this's the shortest path as of Magnolia 5.6 release.

Code Block
collapsetrue
    public void loadJar(File jar2load) {
        try (JarFile jarFile = new JarFile(jar2load)) {
            Enumeration<JarEntry> e = jarFile.entries();
            
            URL url = jar2load.toURI().toURL();
            
            URLClassLoader cl = (URLClassLoader) this.getClass().getClassLoader();
            
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
            method.setAccessible(true); //
            method.invoke(cl, new Object[]{url});
            
            while (e.hasMoreElements()) {
                JarEntry je = e.nextElement();
                if (je.isDirectory() || !je.getName().endsWith(".class")) {
                    continue;
                }
                // -6 because of .class
                String className = je.getName().substring(0, je.getName().length() - 6);
                className = className.replace('/', '.');
                Class<?> c = cl.loadClass(className);
                log.debug("Class loaded: {}", c.getName());
            }
        } catch (Exception e) {
            log.error("Jar file failed to load.");
            log.error(e.getMessage(), e);
        }
    }

...

After successfully loaded module jar file into classpath, we use our existing provided BetwixtModuleDefinitionReader to read it properly

Code Block
collapsetrue
public ModuleDefinition loadModule(String moduleName) throws ModuleManagementException {
        String moduleDescriptorFile = "/META-INF/magnolia/" + moduleName + ".xml";
        BetwixtModuleDefinitionReader moduleReader = new BetwixtModuleDefinitionReader();
        return moduleReader.readFromResource(moduleDescriptorFile);
    }

...

Previously we supported this function as a batch runner which affect all every modules at a time, now we need to split it to run at each module level composition. Here are steps involved:

  1. Get and register module version handler
  2. Process version tasks

Load module repository

We also support refactor this batch effect operator to work on module level. Quite easy, looks like below:

Code Block
collapsetrue
    public void loadModuleRepository(ModuleDefinition def) {
        for (final RepositoryDefinition repDef : def.getRepositories()) {
            final String repositoryName = repDef.getName();
            final String nodetypeFile = repDef.getNodeTypeFile();
            final List<String> wsList = repDef.getWorkspaces();
            String[] workSpaces = wsList.toArray(new String[wsList.size()]);
            loadRepository(repositoryName, nodetypeFile, workSpaces);
        }
    }

...

MavenModuleDefinitionReader.java

A note on load repository on the fly

First we will need RepositoryDefinition from existing repository mapping

Code Block
languagejava
collapsetrue
private info.magnolia.repository.definition.RepositoryDefinition getRepositoryMapping(String repositoryOrLogicalWorkspace) {

        if (!repositoryManager.hasRepository(repositoryOrLogicalWorkspace)) {

            WorkspaceMappingDefinition mapping = repositoryManager.getWorkspaceMapping(repositoryOrLogicalWorkspace);

            repositoryOrLogicalWorkspace = mapping != null ? mapping.getRepositoryName() : repositoryOrLogicalWorkspace;

        }

        return repositoryManager.getRepositoryDefinition(repositoryOrLogicalWorkspace);

    }

If it is null (doesn't exist before) then we'll create a new one.

Create a new repository definition on the fly

Code Block
languagejava
collapsetrue
    final info.magnolia.repository.definition.RepositoryDefinition defaultRepositoryMapping = getRepositoryMapping(DEFAULT_REPOSITORY_NAME);
    final Map<String, String> defaultParameters = defaultRepositoryMapping.getParameters();

    rm = new info.magnolia.repository.definition.RepositoryDefinition();
    rm.setName(repositoryName);
    rm.setProvider(defaultRepositoryMapping.getProvider());
    rm.setLoadOnStartup(true);

    final Map<String, String> parameters = new HashMap<>();
    parameters.putAll(defaultParameters);

    // override changed parameters
    final String bindName = repositoryName + StringUtils.replace(defaultParameters.get("bindName"), "magnolia", "");
    final String repositoryHome = StringUtils.substringBeforeLast(defaultParameters.get("configFile"), "/") + "/" + repositoryName;

    parameters.put("repositoryHome", repositoryHome);
    parameters.put("bindName", bindName);
    parameters.put("customNodeTypes", nodeTypeFile);

    rm.setParameters(parameters);

    try {
        repositoryManager.loadRepository(rm);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
    }

Then register nodetype

Code Block
languagejava
collapsetrue
void registerNodeTypeFile(String repositoryName, String nodeTypeFile) {

        Provider provider = repositoryManager.getRepositoryProvider(repositoryName);

        try {

            provider.registerNodeTypes(nodeTypeFile);

        } catch (RepositoryException e) {

            log.error(e.getMessage(), e);

        }

    }

Load workspace

As simple as calling repositoryManager.loadWorkspace(repositoryName, workspace);