Magnolia 4.5 reached end of life on June 30, 2016. This branch is no longer supported, see End-of-life policy.
Magnolia has a modular architecture where each module performs a particular task or is used to package content and functionality.
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 |
---|---|---|
| Module-specific configuration which will be available via the module class (see below). | Config contains parameters and data for your module. |
| 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. |
| Define a configured dialog. | Dialog to edit the content of sample text & image of component. |
| Define your own dialog control. | Allows editing of the control. |
| 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. |
| Define commands to use with the Magnolia command management. | Activates the command. |
| Define a page to be used in AdminCentral. | For instance, JCR Queries in the Tools menu. |
| Makes the rendering depend on a URI pattern. | Default URI: defines which page should be rendered if no path is specified. |
| 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. |
Element | Description |
---|---|
| The functional name of the module. You will see this name in the config workspace |
| The display name of the module. |
| Full description of the module. |
| The name of a module class |
| The name of the class handling version changes. |
| The module version. |
| Properties that can be used with the version handler. |
| Dependencies on other modules - the module will install only after the specified modules. |
| Servlets which will be included in the servlet filter |
| The repositories which must exist or will be created for this module. |
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.
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.
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.
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.
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>
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() { } }
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.
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 |
|
3.5.4 |
|
3.6 |
|
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.
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):
|
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. |
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
should execute responsibly and respond to issues appropriately.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.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.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.
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);
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) ) )) );
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. |
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
.
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 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.