Magnolia 5.7 reached extended end of life on May 31, 2022. Support for this branch is limited, see End-of-life policy. Please note that to cover the extra maintenance effort, this EEoL period is a paid extension in the life of the branch. Customers who opt for the extended maintenance will need a new license key to run future versions of Magnolia 5.7. If you have any questions or to subscribe to the extended maintenance, please get in touch with your local contact at Magnolia.
This tutorial shows how to configure your own Magnolia bundle and how to include more modules in the bundle. As a best practice, let Magnolia configure your project, don't configure manually. See also Creating a custom bundle and WAR file with multiple configurations.
Prerequisites
- Bundle created in Creating a custom bundle. We use the bundle to explain the concepts in this tutorial. Clone the project from our Git repository.
- Shell such as bash.
- Git and basic Git commands to check out source code.
- IDE such as IntelliJ IDEA or Eclipse to run a Magnolia project. See Creating a custom bundle on how to start a Magnolia project with IntelliJ IDEA.
Clone the project from Git
You have to checkout the project from Magnolia git server and checkout the branch
customize-configuration
.
When you clone the project, you will checkout its "master" branch. On master is the version which we have used for Creating a custom bundle. If you checkout the branch customize-configuration
, you will get the code which we are going to explain on this page.
Open a shell, cd
to a directory where you keep source code and type the following command:
git clone http://git.magnolia-cms.com/git/documentation/example-project.git
When cloning is finished, switch the branch:
git checkout customize-configuration
Set magnolia.repositories.home
Assuming you are working with an IDE using derby as the underlying DB for JCR, you must set magnolia.repositories.home
. Per default the repositories is within the web app, this dierctory might get lost or overridden during the update process which we are going to execute during this example.
The customize-configuration
branch comes with its own magnolia.properties
, it resides in example-project/example-project-webapp/src/main/webapp/WEB-INF/config/
.
Make sure that it points to a valid directory where Magnolia has write permissions. The file on the git repository comes with the following value:
magnolia.repositories.home=/tmp/example-project/repositories
Build the project
Build the project using Maven:
mvn clean install
Run the project
Start the project from your IDE and ensure that it is properly running. You should end up with a running minimal Magnolia project. See Creating a custom bundle > Run the project.
Use case
When developing a custom Magnolia project it often happens that you add a Magnolia module and then realize that its default configuration doesn't fit your requirements. In our case, we add the module fs-browser
. The module comes with a configured property which must be changed.
You could change the property value manually in the Configuration app. The change would work immediately after reopening the app. There is no need to restart your Magnolia instance even. However, you should not change configuration manually for the following reasons:
- Inconvenient. It is inconvenient to modify configuration in the tree once the project is in production. To ensure the availability of your site you would need to deploy a second public instance while you change the first. Always keep changes to production systems minimal.
- Easy to forget. Changing configuration by hand is something you must actively remember to do. You need to write documentation for other developers and administrators so they remember to do the same.
We want to organize our project in a way that the configuration will be set appropriately during the installation.
Now imagine a second use case: You managed to customize configuration during the installation of your custom bundle. But now your system administrator wants you to change the configuration again in the already-deployed production system. You have to change the value of the property again. Avoid making this change manually. Look for way to update the configuration by code.
To change configuration by code we have two tools:
- Installation tasks
- Update tasks
Let's demonstrate this with the fs-browser-app
module.
Adding fs-browser-app
The fs-browser-app (Git source) is small content app that uses the file system as its data source. Consider it an example of a module whose configuration you want to change.
Assuming that your module has a parent pom:
Add the dependency including the version into the
dependencyManagement
section ofexample-project/pom.xml
example-project/pom.xml<dependencyManagement> <dependencies> <!-- more dependencies here... --> <dependency> <groupId>info.magnolia.documentation</groupId> <artifactId>fs-browser-app</artifactId> <version>1.0</version> </dependency> <!-- more dependencies here... --> </dependencies> </dependencyManagement>
Add the dependency without the version into the
dependencies
section ofexample-project/example-project-webapp/pom.xml
example-project/example-project-webapp/pom.xml<dependencies> <!-- more dependencies here... --> <dependency> <groupId>info.magnolia.documentation</groupId> <artifactId>fs-browser-app</artifactId> </dependency> <!-- more dependencies here... --> </dependencies>
After having extended your pom files, restart Tomcat. The new module will be installed. You should see the freshly installed module in the Configuration app.
You see a new app launcher tile labeled "File Browser". You also can find new configuration in /modules/fs-browser-app
in the Configuration app.
Have a look at the property /modules/fs-browser-app/apps/fs-browser/subApps/browser/contentConnector/rootFolder
:
Node name | Value |
---|---|
browser | |
contentConnector | |
rootFolder | /Users/cmeier/tmp/test/ |
The value for the rootFolder
property is bootstrapped by the fs-browser-app. Instead of /Users/cmeier/tmp/test/
you want the root folder of the fs-browser-app map to /your-mounted-nas-drive/current-work/magnolia/
.
That's the value which we want to change.
Customizing configuration by code
As we already mentioned above: Configuring your module manually is not recommended, instead we will:
- Write code to change the configuration. Magnolia provides classes to add, edit, move and remove nodes and properties. The classes implement Task. You don't need to write much code yourself when using these readily available classes.
- Execute code during installation of the module. When installing your custom bundle, pay attention to the order in which your modules is installed. You cannot manipulate data of the
config
workspace if the space is not yet available. - Execute code during update.
A Magnolia Module allows you to do all of the above. The module mechanism provides two things that are important for the task:
- Declare dependencies. By declaring that our project module depends on
fs-browser-app
we ensure that our module is installed afterfs-browser-app
, ensuring we can configure it. - Use a
ModuleVersionHandler
to automate all installation or update tasks our project requires such as changing the
rootFolder
property.
Let's convert our custom module into a Magnolia module.
Convert your project module into a Magnolia module
Magnolia functional modules are a well-known part of the Magnolia ecosystem. Magnolia itself is built as a collection of base modules. But there’s also a lesser-known module type called “project module”. Their purpose is to install or configure your project.
Our custom project at the moment is nothing else then a parent pom plus the sub-module example-project-webapp
.
The project module in our case is the example-project-webapp
. Until now it is just a Maven module. Its src
folder is empty or probably not existing. To change example-project-webapp
into a Magnolia Module, we have to add a module descriptor at least. (And we also will create and register a module version handler class.)
Create a module descriptor
Create a new file example-project-webapp/src/main/resources/META-INF/magnolia/example-project.xml
and add this content:
<!DOCTYPE module SYSTEM "module.dtd"> <module> <name>example-project</name> <displayName>${project.name}</displayName> <description>${project.description}</description> <versionHandler>com.example.myproject.setup.ExampleProjectModuleVersionHandler</versionHandler> <version>${project.version}</version> <dependencies> <dependency> <name>fs-browser-app</name> <version>1.0.1/*</version> </dependency> </dependencies> </module>
In module descriptor we can rely on Maven’s variable substitution to fill in most values. We replicate the pom
‘s dependencies except we use version ranges. Note two things:
- By adding the
<dependency>
fs-browser-app, we ensured, that our custom module itself is installed after the modules we depend on. - Registration of the module version handler class in the
<versionHandler>
-section.
Create a version handler
The versionHandler
element in the module descriptor refers to an not yet existing class. To create a version handler class:
- Create a directory
example-project/example-project-webapp/src/main/java
, if its not already existing. (src/main/java
is the typical folder for Java classes in a Maven project.) - Tell your IDE that the new directory is a source folder. (IntelliJ IDEA, Eclipse)
- Add a new package
com.example.myproject.setup
- Add a new class
ExampleProjectModuleVersionHandler
You should now have a new class file example-project/example-project-webapp/src/main/java/com/example/myproject/setup/ExampleProjectModuleVersionHandler.java
. The class should extend
DefaultModuleVersionHandler
.
package com.example.myproject.setup; import info.magnolia.module.DefaultModuleVersionHandler; /** * This class sets up our great example project. */ public class ExampleProjectModuleVersionHandler extends DefaultModuleVersionHandler { }
The class won't do anything, yet. However you can use it to add tasks for both installing and updating your project.
Install Task
Add an install Task in the ExampleProjectModuleVersionHandler
:
public class ExampleProjectModuleVersionHandler extends DefaultModuleVersionHandler { @Override protected List<Task> getExtraInstallTasks(InstallContext installContext) { ArrayList<Task> tasks = new ArrayList<Task>(); SetPropertyTask setPropertyTask = new SetPropertyTask( "Change the rootFolder property of fs-browser-app contentConnector.", RepositoryConstants.CONFIG, "/modules/fs-browser-app/apps/fs-browser/subApps/browser/contentConnector", "rootFolder", "/your-mounted-nas-drive/current-work/magnolia/"); tasks.add(setPropertyTask); return tasks; } }
The class extends DefaultModuleVersionHandler
which provides a reasonable set of default tasks. Extending
AbstractModuleVersionHandler
is also an option but we don’t really need the other regular module specificities. If you know the difference between the two classes and are familiar with the version handling mechanism, we recommend you extend AbstractModuleVersionHandler
in project modules.
In its default implementation #getExtraInstallTasks
returns an empty list. We override the method and add an instance of
SetPropertyTask
to the install tasks list, which works fine for setting the value of a property.
Test the install Task
To test the extra install Task:
- Shut down the instance.
Delete the repository to ensure your project will be re-installed completely on next start up:
rm -rf example-project/example-project-webapp/target/example-project-webapp-1.0-SNAPSHOT/repositories/magnolia
Or in the root of your project:
mvn clean install
- Start the instance.
Verify that /modules/fs-browser-app/apps/fs-browser/subApps/browser/contentConnector/rootFolder
has the appropriate value as defined in the task.
Update Tasks
If a module is already deployed use an update task:
public ExampleProjectModuleVersionHandler() { register(DeltaBuilder.update("1.1", "Example project 1.1 has a bunch of cool new features.") .addTask(new CheckAndModifyPropertyValueTask( "Set new rootFolder property.", // task name "Setting new rootFolder property.", // task description RepositoryConstants.CONFIG, "/modules/fs-browser-app/apps/fs-browser/subApps/browser/contentConnector", "rootFolder", "/your-mounted-nas-drive/current-work/magnolia/", "/another-mounted-drive/another-directory/")) ); }
Make sure that ExampleProjectModuleVersionHandler
contains the update task, it might be commented out.
In the update task we tell Magnolia: “For version 1.1 of this module, execute these tasks to be executed…” Magnolia knows which version of a module is currently installed by keeping track of the version
property under each module’s node. And it knows what version of a module is being deployed by looking at the module descriptor. Since we use Maven variable substitution in our module descriptor we know that the version number Magnolia reads is the same as our Maven project’s version.
Think about how and when your module is deployed and installed. In this example, it is quite likely that you’ll want to also execute this task when installing the module. Register the same task in getExtraInstallTasks
. Since tasks are stateless descriptors you can easily extract them into class fields, for example. If you look at Magnolia’s own module version handlers you won't find many install tasks because most of the installation work is done via bootstrap files which are extracted on installation by one of the default tasks provided by DefaultModuleVersionHandler
.
When installing a module only install tasks are executed. When updating a module only the update tasks registered for versions between the current and the version we’re updating to are executed.
The logic above only applies to subclasses of AbstractModuleVersionHandler
, and, if needed, you could completely modify this logic by re-implementing the getDeltas
method of your ModuleVersionHandler
.
Test the update Task
Before running the update (test) check the current version of the installed project in the Configuration app. It should be 1.0-SNAPSHOT.
If there is another value such as ${project.version}
change it to 1.0-SNAPSHOT.
The update task will be executed only when the version of the module (with the version handler) rises up to 1.1. But when looking at the pom we realize, that the pom of the project (on the branch customize-configuration) has the same version like on master. Since the module never was released, 1.0-SNAPSHOT is correct.
To test the update task, beside other things, we have to emulate that our custom module already reached version 1.1 (or 1.1-SNAPSHOT).
- Stop the server.
- Change the version from 1.0-SNAPSHOT to 1.1-SNAPSHOT in both
example-project/pom.xml
(line 12) andexample-project/example-project-webapp/pom.xml
(line 7). - Make sure the changes get deployed and restart the server.
- Follow the update process in the browser.
- Check the changed property with the Configuration app in
/modules/fs-browser-app/apps/fs-browser/subApps/browser/contentConnector@rootFolder
.
In a real situation, you would raise the version during a release process.
The future of your project
Managing updates and releases is a lot of work. Take advantage of Magnolia's update mechanism.
Use a stable version numbering scheme for your (custom) modules. A proper release process (Maven’s release plugin) helps for updates of connected and production systems. It is tedious to update your development environment but discipline helps. Remember to write update tasks for all needed changes and test them regularly. Write update tasks immediately. Use the info.magnolia.module.ModuleVersionHandlerTestCase to write unit tests for your module version handlers. And please submit your improvements.
This is also an exercise in dependency management. You’ll need to figure out when to update to Magnolia’s newest and latest release, and figure out what that means for your module. In most cases however, Magnolia itself will handle the update nicely. Everytime when there is a change in the configuration of a Magnolia module, the ModuleVersionHandler
of the module will take care of updating your configuration.
Conclusion
We now have a project which not only has a stable and reproducible build, but which can also be installed and deployed right away by your peers. Hopefully you’ll be able to reuse ideas to better manage your project’s lifecycle by now. If not, feel free to comment on what doesn’t work for you, or what could be clarified or expanded upon.