The 5.7 branch of Magnolia reached End-of-Life on December 31, 2023, as specified in our End-of-life policy. This means the 5.7 branch is no longer maintained or supported. Please upgrade to the latest Magnolia release. By upgrading, you will get the latest release of Magnolia featuring significant improvements to the author and developer experience. For a successful upgrade, please consult our Magnolia 6.2 documentation. If you need help, please contact info@magnolia-cms.com.

Magnolia Groovy module adds Groovy capabilities to Magnolia. Groovy is a popular dynamic language for the JVM. To know more about it, please visit the Groovy official website which has plenty of tutorials and excellent documentation both for beginners and advanced users of the language.

The module provides a web based Unix-like console where you can access contents in Magnolia repositories in a "groovish" way, a scripts repository where you can store your scripts and the ability to plugin Groovy classes into Magnolia at runtime, without the need for deploying them and restarting the servlet container. All these tools make for a more agile approach to coding and maintaining Magnolia based websites.

Magnolia 5.7 updates magnolia-module-groovy to version 2.7, which ships the latest stable version of Apache Groovy 2.5.

Earlier versions of Groovy were delivered in a single jar file called groovy-all but this library does not exist anymore. magnolia-module-groovy-2.7 now ships with only the minimum set of libraries:

  • groovy-2.5.0.jar
  • groovy-xml-2.5.0.jar

Some of your custom Groovy scripts may require additional libraries.

<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy</artifactId>
  <version>2.5.0</version>
</dependency>
<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy-xml</artifactId>
  <version>2.5.0</version>
</dependency>

Installing

Maven is the easiest way to install the module. Add the following to your bundle. The parent POM of your webapp project should set the latest version of the module automatically. Should you need to use a specific module version, you can define it using the <version/> in the dependency.

Note the changes in groupId and artifactId since the 2.6 release.

<dependency>
  <groupId>info.magnolia.groovy</groupId>
  <artifactId>magnolia-groovy</artifactId>
</dependency>

Compatibility module

We have been gradually removing the old Content API from our modules since Magnolia 5.6. If you have custom code relying on classes from the old groovy module then you must do one of two things:

If you have Groovy scripts or classes which rely on the Content API, then you will need the compatibility module to run them. You are encouraged to update to Node API.

  • Update your code for the new version of the groovy module.
  • Or you can use the magnolia-groovy-compatibility module together with the magnolia-core-compatibility module.

With maven:

Add the following snippet to you pom file:

<dependency>
  <groupId>info.magnolia.groovy</groupId>
  <artifactId>magnolia-groovy-compatibility</artifactId>
</dependency>

Usage

Repository data navigation simplified

Thanks to the . (dot) notation and other handy shortcuts such as .nodes (alternatively .children), .properties.metaData and .parent you can read any content node and property, change their values, create nodes and delete them, all with clean, concise syntax. The module is shipped with some preinstalled scripts that demonstrate most of these features. As an example, here is how to navigate to and print the node data named title:

session = ctx.getJCRSession('website')
node = session.getNode('/travel')
node.about.title

One noticeable thing about the above snippet is that it is all you need to write: no imports, no need to catch exceptions, etc. Compare it to the Java code that achieves the same result.

import info.magnolia.context.MgnlContext;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

public class Test {
    public static void main(String[] args) {
        try {
            Session session = MgnlContext.getJCRSession("website");
            Node node = session.getNode("/travel/about");
            String value = node.getProperty("title").getString();
            System.out.println(value);
        } catch (RepositoryException e) {
            e.printStackTrace();
        }
    }
}

And, most importantly, with Groovy you do not need to deploy your class and restart the server. As an additional bonus, when using the Groovy Unix-like shell which comes with the module, when you navigate a repository, by calling the print() method on a node its children and properties are shown in the output, giving you an overview of the data structure. The screenshot below illustrates this.

Using the print() method differs from previous versions where the data structure was displayed automatically on pressing the Enter key. However, in many cases that caused the output to be unnecessarily verbose. What is printed now by default is simply the node path.


Being able to navigate a repository data like this can come in handy, for instance, when writing and testing your own scripts or trying things out. Or when you need to use the Rescue App  because the Magnolia UI, for whatever reason, is not available.

As node here is basically an instance of a JCR Node coated in a special "groovysh" wrapper, you to call any of the Node interface methods and take advantage of the Groovy goodies at the same time.

node.about.properties.each { println it.name } 
println node.about.parent //(this can be null)

If you want to print out all node properties but jcr ones, you can do:

node.about.properties.findAll { !it.name.startsWith('jcr:') }.each{ println it.name }

Built-in Help

Type help (or ?) followed by Enter  to view the built-in help (including keyboard shortcuts) for the Groovy shell.

Autocompletion & Keyboard Shortcuts

Autocompletion in the Groovy console:

  • Start writing a variable name and press Tab to autocomplete available variables in script context.
  • Press Tab Tab to discover available variables in script context.
  • Type . (dot) after a context variable and press Tab to autocomplete method names.
  • Type . (dot) after a context variable and press Tab Tab to discover method names.

The ctx context object is always available. In Groovy it represents MgnlGroovyConsoleContext, a special instance of Magnolia's Context.

Multiline code in the console

To enter multiline code in the Groovy console, press Shift + Enter at the end of each line. This enters the multiline mode. Once you've entered a block of text, press Enter at the end of the command to run it. This is useful when writing Groovy statements more complex than a simple one-liner.

BindingDescription
TAB Trigger autocompletion of variables names and object methods
TAB TAB Discover all available suggestions, if any, for the current identifier 
SHIFT  + ENTER Activate multi-line mode in the console. To run the code, hit the ENTER  key
   or  CTRL  +  P  Show previous command from history
   or   CTRL  +  N  Show next command from history
  CTRL  +   R  Begin reverse search through history. Type characters to find matching commands.
  CTRL  +   G  Cancel reverse search
 or   CTRL  +  B  Move cursor one character left
 or   CTRL  +  F  Move cursor one character right
HOME  or   CTRL  +  A  Move to beginning of line
END  or   CTRL  +  E  Move to end of line

Create and update properties

It is also possible to assign values to properties or create new ones.

node.foo = 3.14d
node.bar = true
node.baz = 'some text'
node.qux = 100

This will assign the values on the right hand side to the properties on left hand side. Should those not exist, they will be automatically created. Moreover, the correct type will be detected based on the value assigned (Boolean, String, Long or Double).

All the above assignments will be in-memory only unless explicitly persisted via a call to save() on the current JCR session.

Use Groovy classes instead of Java classes

This feature allows you to virtually replace every Magnolia Java class with a Groovy one. Although not a major issue for most tasks, due to its dynamic nature Groovy is slower than Java on the first run. This because groovy code must be compiled into byte-code before it can be ran on the JVM. Nevertheless, replacing classes can come in handy in situations where you cannot deploy and restart the server, but need to quickly fix or add a piece of logic.

Two Apache Ant version 1.9.2 jars are required to test this example. Download Ant 1.9.2 and extract the files. Add ant.jar and ant-launcher.jar. to your file system at /<CATALINA_HOME>/webapps/<contextPath>/WEB-INF/lib and restart Magnolia.

The Groovy module ships with a sample class sample.commands.GroovyMailCommand that sends an email. Here is how you could use it to create a new scheduled job on-the-fly using a Groovy command and the Scheduler module.

The GroovyMailCommand is accessible in Dev > Groovy /samples/commands/GroovyMailCommand.

GroovyMailCommand
package samples.commands
import info.magnolia.commands.*

/*
* This groovy class is an example that serves to show how it can be used as a replacement of a traditional Java class on the fly.
*/
public class GroovyMailCommand extends MgnlCommand {

    public boolean execute(Context ctx) {

        def ant = new AntBuilder()
        def buildname = ctx.get('buildname')
        ant.mail(mailhost:'mailhost', mailport:'25', subject: ctx.get('subject')) {
           from(address:ctx.get('from'))
           to(address:ctx.get('to'))
           message("The ${buildname} nightly build has completed")
        }
    }
}

(warning) Replace mailhost with your SMTP server address.

Create a command configuration in the Configuration app as shown below. We added it to the Mail module in /modules/mail/commands/default, but you can add it to your own module or another of your choice. Specify the fully qualified name of the Groovy class (matching the path in the repository where it is saved samples.commands) as if it were a plain Java class (which it actually is).

Node nameValue
 
modules

 
mail


 
commands


 
default


 
sendMail


 
sendMailWithGroovy


 
class

samples.commands.GroovyMailCommand

Create a scheduled job configuration referring to the command in the Configuration app > /modules/scheduler/config/jobs. The parameters will be used at runtime by the Groovy script.

Node nameValue
 
modules

 
scheduler


 
config


 
jobs


 
demo


 
sendMail


 
params


 
buildname

TEST123

 
from

me@me.com

 
subject

Hello

 
to

you@you.com

 
active

true

 
catalog

default

 
command

sendMailWithGroovy

 
cron

0 0 * * * *

 
description

send mail every hour

Replace the from and to email addresses with actual addresses.

You can use this command to replace a similar Java command at runtime. Or you can edit it and have it compiled and replaced on-the-fly (a kind of class hot deploy). This relies on the Magnolia observation mechanism. Every time the value of the class node data changes the Groovy class will be recompiled.

Rescue App

The MgnlGroovyRescueApp is a special Vaadin app that can be used to bypass the Magnolia filter chain. This is useful when you need to perform rescue operations on a corrupted Magnolia instance or when the Magnolia UI is not loading. To enable the servlet you must explicitly comment out the Magnolia filter chain in the web.xml file and register the Groovy Rescue App. 

All operations performed in the Groovy Rescue App are executed in system context, meaning no security restrictions are enforced. This might expose your data to risk of irreversible damages if you are not aware of what you are doing. In other words, use it at your own risk.

Register the Groovy Rescue App

  1. Stop Magnolia
  2. Open /<CATALINA_HOME>/webapps/<contextPath>/WEB-INF/web.xml in a text editor.
  3. Comment out the filter and filter-mapping sections:

    <!--
    <filter>
       <display-name>Magnolia global filters</display-name>
       <filter-name>magnoliaFilterChain</filter-name>
       <filter-class>info.magnolia.cms.filters.MgnlMainFilter</filter-class>
    </filter>
    <filter-mapping>
       <filter-name>magnoliaFilterChain</filter-name>
       <url-pattern>/*</url-pattern>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    -->
    
  4. Add the following lines to web.xml in order to register the Groovy Rescue App:

    <servlet>
    <servlet-name>Vaadin</servlet-name>
      <servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
        <init-param>
          <description>Groovy Rescue App</description>
          <param-name>UI</param-name>
          <param-value>info.magnolia.module.groovy.rescue.MgnlGroovyRescueApp</param-value>
       </init-param>
       <init-param>
          <param-name>widgetset</param-name>
    <!--      <param-value>info.magnolia.widgetset.MagnoliaWidgetSet</param-value> -->
    <!--      <param-value>info.magnolia.widgetset.MagnoliaProWidgetSet</param-value> -->
       </init-param></servlet>
    <servlet-mapping>
      <servlet-name>Vaadin</servlet-name>
      <url-pattern>/*</url-pattern>
    </servlet-mapping>
    
  5. Uncomment:

    • <param-value>info.magnolia.widgetset.MagnoliaWidgetSet</param-value> - when using Magnolia CE or EE Standard.
    • <param-value>info.magnolia.widgetset.MagnoliaProWidgetSet</param-value> - when using Magnolia EE Professional.
  6. Save the web.xml.
  7. Start Magnolia.
  8. Open a Web browser and access the Groovy Rescue App at http://host/<contextPath>

Make the required changes

Use Groovy commands to navigate to the data you want to change.

Example 1: Deleting an erroneous configuration node untitled from /config/modules/someModule/virtualURIMapping.

  1. Assign Session of config repository to session.

    session = ctx.getJCRSession('config')
    
  2. Assign the parent node content to root.

    root = session.getNode('/modules/someModule/virtualURIMapping/')
    
  3. Get and remove the child node untitled.

    root.getNode('untitled').remove()
    
  4. Save the session.

    session.save()
    

    (warning) Always remember to save the session after modifying a node.

Example 2: Disabling the I18nContentSupportFilter filter.

mgnl> session = ctx.getJCRSession('config')
====> session-admin-213
mgnl> root = session.getNode('/server/filters/i18n')
====> node /server/filters/i18n
mgnl> root.getProperty('enabled').getString()
====> true
mgnl> root.setProperty('enabled', 'false')
====> property /server/filters/i18n/enabled
mgnl> root.getProperty('enabled').getString()
====> false
mgnl> session.save()

  • Line 3: Assign the content of the /server/filters/i18n node to root.
  • Line 5: Check the current setting of the enabled property.
  • Line 7: Set the property to false.
  • Line 9: Verify that the configuration has been changed.
  • Line 11: (warning) Save the session.

Deregister Groovy Rescue App

  1. In web.xml, remove the servlet registration lines you added above.
  2. Remove comments from the filter and filter-mapping sections.
  3. Save web.xml.

Tomcat should notice that web.xml changed and read the changes automatically. If this does not happen, stop Magnolia, edit web.xml, and start Magnolia again. Then try to access Magnolia.

Further resources