Magnolia has a modular architecture where each module performs a particular task or is used to package content and functionality.

Module configuration items

Magnolia provides standard module configuration items such as controls, template types (pages, areas, components), and dialogs. Customized implementations of configuration items need to be configured for the relevant module. The config node uses Node2Bean to populate all registered module classes as Java Beans with the content of the config node. Because Magnolia monitors configured items, any changes in the configuration of registered items will immediately be active.

Configuration item

Function of Configuration Item

Details or Example

config

Module-specific configuration which will be available via the module class (see below).

Config contains parameters and data for your module.

templates

Template definition. Templates can be pages or components. They can use FreeMarker, JSP or a custom templating language.

For example, stkNews is a template definition provided in the Magnolia Standard Templating Kit (STK). Its purpose is to render a news page.

dialogs

Define a configured dialog.

Dialog to edit the content of sample text & image of component.

controls

Define your own dialog control.

Allows editing of the control.

trees

Define a tree to be used in AdminCentral and when browsing in dialogs.

Defines where in the tree will be displayed in the Website view of AdminCentral.

commands

Define commands to use with the Magnolia command management.

Activates the command.

pages

Define a page to be used in AdminCentral.

For instance, JCR Queries in the Tools menu.

virtualURIMapping

Makes the rendering depend on a URI pattern.

Default URI: defines which page should be rendered if no path is specified.

rendererType

Defines a new template type and determines the renderer to use.

Magnolia ships with JSP and FreeMarker template renderers. You can also integrate logic before rendering a page, area or component by using a custom renderer or injecting your implementation of the standard renderers. It is possible to create a custom renderer for another templating language, e.g. Velocity.

Module descriptor elements

Element

Description

name

The functional name of the module. You will see this name in the config workspace

displayName

The display name of the module.

description

Full description of the module.

class

The name of a module class

versionHandler

The name of the class handling version changes.

version

The module version.

properties

Properties that can be used with the version handler.

dependencies

Dependencies on other modules - the module will install only after the specified modules.

servlets

Servlets which will be included in the servlet filter

repositories

The repositories which must exist or will be created for this module.

Properties

Properties in the module descriptor can be used to define values for install tasks. You can get the current module descriptor from the InstallContext:

installContext.getCurrentModuleDefinition().getProperty(PROPNAME);

The properties defined in the module descriptor will be included in the Magnolia system properties too, so you might want to use the SystemProperty class instead:

SystemProperty.getProperty(PROPNAME, "default");

Additional details will be described in a separate chapter.

Java object components

Magnolia uses dependency injection. This means that object dependencies are injected automatically at the time the object is instantiated. The benefit of injecting dependencies in this way is that you can change the implementation of the depended object without having to change the way dependencies are retrieved. A further benefit is testability.

This type of dependency is different from module dependencies. It is a code/object level dependency that defines the relationship between objects, not a dependency between modules.

What is inversion of control?

The term dependency injection (DI) is often used together with inversion of control (IoC). IoC is the principle and DI is the implementation. The concepts are related but at Magnolia we talk only about DI. To learn how IoC was implemented at Magnolia, see Concept IoC in Magnolia.

Defining components

Components in Magnolia are defined in the module descriptor XML file. Modules can define components of the same type. A definition in a module takes precedence over definitions in modules that it depends on. This allows modules to override and customize components in other modules.

For example, suppose you want to implement a MailSender component. If another Magnolia module already implements such a component, you can override it by declaring a module dependency on the module that provides the original MailSender component and by making sure that your component definition uses the same type.

Module descriptor

The module descriptor can specify components for different containers. This is done using the id element. You will rarely use a container other than main.

This example module descriptor defines a component of type GeoService which is an interface. The actual implementation is GeoServiceImpl.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module SYSTEM "module.dtd">
<module>
  <name>my-module</name>
  <version>1.0</version>
  <components>
    <id>main</id> <!-- Container ID --> 
    <component>
      <type>com.acme.geo.GeoService</type>
      <implementation>com.acme.geo.GeoServiceImpl</implementation>
    </component>
  </components>
</module>

How to inject dependencies

To retrieve a component, declare a dependency on it. Here is an example declaring a dependency to a component that needs to be injected (GeoService). The example uses the constructor injection type. Field and setter injection are also supported.

public class SomeGeoAwareModel extends RenderingModelImpl {
  private final GeoService geoService;
  @Inject
  public SomeGeoAwareModel(GeoService geoService) {
    this.geoService = geoService;
  }
  public String execute() {
  }
}

Module version handling

The module mechanism facilitates smoother 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. Upon each startup of Magnolia the version handler provides a list of deltas.

Delta

A Delta is a list of Tasks to be executed during a version change. The sample module's version handler provides three Deltas which can be referred to as an example.

Delta (from Version)

Task

3.5

  • Remove menu item for sample templates.
  • Add menu item for sample templates.
  • Remove menu item for sample components.
  • Add menu item for sample components.
  • Remove menu item for sample dialogs.
  • Add menu item for sample dialogs.
  • Install the CSS configuration.
  • Install the JS configuration.
  • Install the paragraph configuration.

3.5.4

  • Remove Kupu editor.

3.6

  • Replace if exists user "bob" with "david".
  • Replace if exists user "joe" with "eve".
  • Replace if exists user "melinda" with "patrick".

More precisely, a Delta can store and apply an additional set of Conditions to be executed only when a version update or revision is deployed. The Tasks of a Delta will only be executed if all specified Conditions are met. We will examine Tasks and Conditions in more detail later in this chapter.

Abstract version handler classes

To create your own version handler you do not have to start from scratch. There are some abstract classes for common cases which can be used. Good practice is to extend one these base classes and implement only the methods you need. There are two abstract version handler classes in the package info.magnolia.module which can be used to create a custom version handler.

Class

Description

AbstractModuleVersionHandler

Extend this and register your Deltas in the constructor using the register method. Add your own install tasks by overriding the getExtraInstallTasks() method. In most cases, modules won't need to override any other method.

DefaultModuleVersionHandler

Extends the AbstractModuleVersionHandler and triggers the most common installation tasks (see table above):

  • register repositories, nodetypes and workspaces as stated in the module definition
  • bootstrap the module's mgnl-bootstrap files
  • bootstrap the module's mgnl-bootstrap-samples files
  • extract the module's mgnl-files files
  • register the module's servlets.

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

Task

Description

SetupModuleRepositoriesTask

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

ModuleBootstrapTask

This task bootstraps the necessary module repository content which is provided as multiple XML-export files under "/mgnl-bootstrap/moduleName".

SamplesBootstrapTask

This task bootstraps the module's sample repository content which is provided as multiple XML-export files under "/mgnl-bootstrap-samples/moduleName".

ModuleFilesExtraction

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

RegisterModuleServletsTask

This task registers the necessary servlets for this module.

Task

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;
  • 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 still shows to the end user.
  • Exception messages should be simple and intuitive.

There is already a set of predefined and abstract Tasks available in the package info.magnolia.module.delta which can be used. Here are some of the most useful: AbstractTask , AbstractRepositoryTask , AbstractConditionalRepositoryTask , AllChildrenNodesOperation , AllModulesNodeOperation .

Also interesting are delegate Tasks:

ArrayDelegateTask ,
ConditionalDelegateTask ,
PropertyExistsDelegateTask ,
PropertyValueDelegateTask .

For the complete list of Tasks, please consult the  package-summary javadoc. 

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

Condition

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, etc. Modules register their Conditions like their Tasks, for each successive version. Only if all Conditions in 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 node builder API: package-summary , is commonly used in update tasks.

Here's a snippet from STKModuleVersionHandler 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",
       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",
       getNode("sectionHeader").then(
           addProperty("editable", Boolean.FALSE)
       ),
       getNode("footer").then(
           addProperty("editable", Boolean.FALSE)
       ),
       getNode("branding/areas/metaNavigation").then(
               addProperty("editable", Boolean.FALSE)
       )
   ))
 );

Module dependencies

You can define runtime or install time dependencies (not build dependencies). If you define a dependency on a module, then this module will be installed and started before your module. A dependency can be optional which means that if an optional module is not present, installation will proceed, if the optional module is present, this module will be installed first. The dependencies could look like this:

<dependencies>
    <dependency>
      <name>core</name>
      <version>3.6.0/*</version>
    </dependency>
    <!--  in case cache module is present, 
    make sure we install after so we can add a bypass -->
    <dependency>
      <name>cache</name>
      <version>3.6.0/*</version>
      <optional>true</optional>
    </dependency>
  </dependencies>

The syntax for a version is from version/to version where a version number consist of three parts: major version.minor version.patch level where the latter two parts are optional. For a strict version dependency just use one version string.

Taking * for the from version means "to version or lower".

Taking * for the to version means "from version or higher".

Some examples:

Example

Description

3

Must have major version 3.

3.6

Must have major version 3 and minor version 6.

3.6.3

Must have major version 3 and minor version 6 and patch level 3.

3/*

Must have major version 3 or higher.

3.6/*

Must have major version 3 and minor version 6 or higher.

3.6.3/*

Must have major version 3 and minor version 6 and patch level 3 or higher.

*/3

Must have major version 3 or lower.

*/3.6

Must have major version 3 and minor version 6 or lower.

*/3.6.3

Must have major version 3 and minor version 6 and patch level 3 or lower.

3.5/3.6.2

Version not lower than 3.5 and not higher than 3.6.2.

Servlets

Instead of defining servlets in web.xml, servlets can be defined in the module descriptor using the same values as in web.xml. The DMS module for example registers a servlet to deliver the documents:

  <servlets>
    <servlet>
      <name>DMSDownloadServlet</name>
      <class>info.magnolia.module.dms.DMSDownloadServlet</class>
      <comment>DMS download servlet</comment>
      <mappings>
        <mapping>/dms/*</mapping>
        <mapping>/dms-static/*</mapping>
      </mappings>
    </servlet>
  </servlets>

These servlets are actually registered through the RegisterModuleServletsTask at install time; this means that if the definition changes over time, the module developer should take care of updating the configuration by using a ModuleVersionHandler.

Servlet initialization parameters

You can specify initialization parameters for servlets in the module descriptor file. These parameters are the equivalent of servlet initialization parameters in a web.xml file. Use the following syntax in the descriptor:

<params>
  <param>
    <name>name</name>
    <value>value</value>
  </param>
</params>

Example:

Repositories

Repositories and/or workspaces can be defined in the module descriptor. If necessary Magnolia initializes these workspaces before the installation starts. As an example look at the data module:

  <repositories>
    <repository>
      <name>magnolia</name>
      <workspaces>
        <workspace>data</workspace>
      </workspaces>
      <nodeTypeFile>/mgnl-nodetypes/magnolia-module-data-nodetypes.xml</nodeTypeFile>
    </repository>
  </repositories>

Under the hood, these repository workspaces are registered and initialized by the SetupModuleRepositoriesTask when the module is installed.

#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))