Maintaining your module

Magnolia’s module mechanism facilitates smooth version changes. The idea is that an upgrade or update should be as easy as replacing a module JAR. Magnolia uses the version handler to determine which tasks should be executed. On each startup of Magnolia, the version handler provides a list of deltas.

The info.magnolia.module.ModuleVersionHandler interface is a special class which contains a series of tasks (deltas) which are executed during installation or during specific updates.

If your module needs to handle its own installation and updates, you should provide an implementation of this interface.

Execute delta tasks

To maintain your module, you need to excecute a versionHandler that runs a task list of Deltas.

What is a Delta?

A Delta is essentially a list of tasks that are executed during a version change. This Delta stores and applies a set of tasks that meet the specified conditions. It will only be run it all conditions are met.

Sample module version handler

package info.magnolia.module.samples.setup;

import static info.magnolia.repository.RepositoryConstants.WEBSITE;

import info.magnolia.module.DefaultModuleVersionHandler;
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.ArrayDelegateTask;
import info.magnolia.module.delta.BootstrapSingleModuleResource;
import info.magnolia.module.delta.BootstrapSingleResource;
import info.magnolia.module.delta.DeltaBuilder;
import info.magnolia.module.delta.IsInstallSamplesTask;
import info.magnolia.module.delta.NodeExistsDelegateTask;
import info.magnolia.module.delta.RemoveNodeTask;
import info.magnolia.module.delta.Task;

import java.util.ArrayList;
import java.util.List;

import javax.jcr.ImportUUIDBehavior;


/**
 * Used to update previous version of samples module to the new one, as the module has major changes
 * there are some tasks to perform.
 */
public class SamplesVersionHandler extends DefaultModuleVersionHandler {

    public SamplesVersionHandler() {
        register(DeltaBuilder.update("5.4.1", "")
                .addTask(new ArrayDelegateTask("Remove samples configuration from JCR",
                        new NodeExistsDelegateTask("", "/modules/samples/dialogs",
                        new RemoveNodeTask("", "/modules/samples/dialogs")),
                        new NodeExistsDelegateTask("", "/modules/samples/templates",
                        new RemoveNodeTask("", "/modules/samples/templates")),
                        new NodeExistsDelegateTask("", "/modules/samples/virtualURIMapping",
                                new RemoveNodeTask("", "/modules/samples/virtualURIMapping"))))
                .addTasks(getCommonTasks()));

        register(DeltaBuilder.update("6.0", "")
                .addTask(new RemoveNodeTask("Remove JSP sample site", "Magnolia Templating JSP is deprecated as of 6.0.",
                        WEBSITE, "/jsp-sample-site")));
    }

    protected List<Task> getCommonTasks() {
        final List<Task> commonTasks = new ArrayList<>();
        commonTasks.add(new IsInstallSamplesTask("Re-Bootstrap website content for sample pages", "Re-bootstrap website content to account for all changes",
                new ArrayDelegateTask("",
                        new BootstrapSingleResource("", "", "/mgnl-bootstrap-samples/samples/website.ftl-sample-site.xml"),
                        new BootstrapSingleResource("", "", "/mgnl-bootstrap-samples/samples/dam.samples.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING))));
                // We re-bootstrap twice because a simple (and single) re-bootstrap (using ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING) would NOT
                // "move" an existing site definition (which might actually exist from a previous version) in the site module
        commonTasks.add(new BootstrapSingleModuleResource("config.modules.samples.config.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING));
        commonTasks.add(new BootstrapSingleModuleResource("config.modules.samples.config.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW));

        return commonTasks;
    }

    /**
     * Installation process will bootstrap everything in the bootstrap folder, then we have to add some extra tasks.
     */
    @Override
    protected List<Task> getExtraInstallTasks(InstallContext installContext) {
        final List<Task> extraTasks = new ArrayList<>();
        extraTasks.addAll(getCommonTasks());
        return extraTasks;
    }

}

Version Handler classes

To create your own version handler, you don’t have to start from scratch. There are some abstract classes for common cases which can be used.

It’s best to extend one these base classes and implement only the methods you need instead of going at it from scratch.

There are two abstract version handler classes in the package info.magnolia.module which can be used to create a custom version handler.

  • AbstractModuleVersionHandler

  • DefaultModuleVersionHandler

You can extend this class and register your deltas in the constructor using the register method.

  1. Add your own install tasks by overriding the getExtraInstallTasks() method.

    In most cases, modules won’t need to override any other method.

If you do not specify a version handler in the module descriptor, DefaultModuleVersionHandler is used and performs some basic install tasks.

This class extends AbstractModuleVersionHandler and triggers the most common installation tasks such as:

Task Description

info.magnolia.module.delta.SetupModuleRepositoriesTask

Bootstraps empty repositories defined in the module descriptor, grants them to the superuser and subscribes them so that activation can be used.

info.magnolia.module.delta.ModuleBootstrapTask

Bootstraps the necessary module repository content which is provided as multiple XML-export files under /mgnl-bootstrap/<moduleName>.

info.magnolia.module.delta.SamplesBootstrapTask

Bootstraps the module’s sample repository content which is provided as multiple XML-export files under /mgnl-bootstrap-samples/<moduleName>.

info.magnolia.module.delta.ModuleFilesExtraction

Copies all files under mgnl-files which includes the module name as a directory to the web application folder, preserving the path.

info.magnolia.module.delta.RegisterModuleServletsTask

Registers the necessary servlets for the module.

Tasks

A Task is a lightweight class with the minimal necessary code to augment configuration during module installation. The important method in the Task interface is:

void execute(InstallContext installContext) throws TaskExecutionException;
Task guidelines
  • A Task should execute responsibly and respond to issues appropriately.

  • To allow developers/users to fix issues at a later time, fixable or irrelevant issues should be logged and standard InstallContext methods used.

  • A Task should be in place to perform backups of nodes when extensive modifications are performed, meaning that a user can refer to a pre-alteration copy.

  • In the event of an unrecoverable issue, a Task should automatically perform a TaskExecutionException to interrupt and cancel the module installation, update and startup. If a TaskExecutionException is thrown, the exception message should be shown to the end user.

  • Exception messages should be simple and intuitive.

Abstract tasks

A set of predefined and abstract Tasks is available in the info.magnolia.module.delta package which can be used. Here are some of the most useful:

Task Description

info.magnolia.module.delta.AbstractTask

Abstract implementation of the Task interface which handles the mandatory properties, name and description.

info.magnolia.module.delta.AbstractRepositoryTask

An abstract implementation of AbstractTask which wraps the execute call in a try/catch block.

This avoids verbose and irrelevant code lines in actual tasks and leaves room for smarter and more interesting exception handling.

info.magnolia.module.delta.AbstractConditionalRepositoryTask

An abstract implementation of a RepositoryTask that only needs to be executed when a specific node is not found in the repository.

This can be used to easily create self-check tasks for mandatory configuration.

info.magnolia.module.delta.AllChildrenTask

Executes the abstract method on every child node.

Delegate tasks

Here is a list of useful delegate tasks.

Task Description

info.magnolia.module.delta.ArrayDelegateTask

A task that simply delegates to an array of other tasks.

info.magnolia.module.delta.ConditionalDelegateTask

A task that delegates to another if a condition is true, or to an optional other if it is false.

info.magnolia.module.delta.HasPropertyDelegateTask

A task that delegates to another depending on whether a specified property exists or not.

info.magnolia.module.delta.ValueOfPropertyDelegateTask

A task which delegates to another if a property has a given value.

It is customary for modules to expose some tasks that can be re-used by other modules when needed, such as info.magnolia.module.data.setup.RegisterNodeTypeTask. The API is designed so that it should be easy for you to write your own specific Task implementations.

Conditions

Conditions are checked prior to the installation or update of a module. They check for system configuration which can’t be automatically updated, like configuration, dependencies, and so forth. Modules register their conditions like their tasks, for each successive version.

Only if all conditions in the delta evaluate positively will the tasks of the delta be executed. The most important method in the Condition interface is:

boolean check(InstallContext installContext);

Node builder API

The info.magnolia.jcr.nodebuilder.NodeBuilder is commonly used in update tasks.

Here’s a snippet from info.magnolia.module.templatingkit.setup.STKModuleVersionHandle that demonstrates its use:

register(DeltaBuilder.update("2.0.1", "")
   .addTask(new NodeBuilderTask("Add editable property to stkSection page", "Adds and sets editable property to true for sectionHeader area on stkSection pages.", ErrorHandling.logging, RepositoryConstants.CONFIG, "/modules/standard-templating-kit/templates/pages/stkSection/areas", (1)
       getNode("sectionHeader").then(
           addProperty("editable", Boolean.TRUE)
       )
   ))
   .addTask(new NodeBuilderTask("Add editable property to areas", "Adds and sets editable property to false on sectionHeader, footer and metaNavigation.", ErrorHandling.logging, RepositoryConstants.CONFIG, "/modules/standard-templating-kit/config/site/templates/prototype/areas", (2)
       getNode("sectionHeader").then(
           addProperty("editable", Boolean.FALSE)
       ),
       getNode("footer").then(
           addProperty("editable", Boolean.FALSE)
       ),
       getNode("branding/areas/metaNavigation").then(
               addProperty("editable", Boolean.FALSE)
       )
   ))
 );
1 Here, a task is added to add the editable property to the sectionHeader.
2 Here, a task is added to add the editable property to the sectionHeader, footer, and the branding/areas/metaNavigation.

Module descriptor properties

Properties in the module descriptor can be used to define values for install tasks.

Option 1: InstallContext

You can get the current module descriptor from InstallContext:

installContext.getCurrentModuleDefinition().getProperty(PROPNAME);

Options 2: MagnoliaConfigurationProperties

The properties defined in the module descriptor are included in your Magnolia system properties as well, so you might want to use the SystemProperty class instead:

SystemProperty.getProperty(PROPNAME, "default");
Feedback

DX Core

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules