Creating rules and listeners

November 14, 2023

Introduction

Since using a Java Content repository Jahia makes use of the standard JCR ObservationManager. This manager observes events on the content repository and passes them to several event listeners implementing the abstract DefaultEventListener class.

In the JCRStoreService Jahia configures several default event listeners, which are necessary to make Jahia work. These event listeners are then registered to the JCRObservationManager.

One such event listener is the JBoss Rules Listener, which allows integrators to use rules to process events. These rules can be deployed in modules (template-sets), but Jahia also adds some default rules into the core product.

Furthermore the JCRStoreService holds a configurable interceptor-chain of PropertyInterceptor implementations, which are called on getting, setting or removing of properties in nodes through Jahia's JCR node/property wrappers.

events_diagram.png

Events on content can be observed through the JCR API. System events, not directly related to content operation, are sent and received via spring.

Rules

Each module in Jahia can define its own rules, extend the rule domain specific language and add its own set of global objects.

How to add rules in a module?

Just define a rules.drl file in the resources/META-INF folder of the module. An example from the tasks module:

package org.jahia.modules.tasks.rules

//list any import classes here.
import org.jahia.services.content.rules.*
import org.slf4j.Logger

expander rules.dsl

//declare any global variables here
global User user
global Service service
global Logger logger
global Tasks tasks

rule "A welcome task for the new user"
   when
       A new node is created
                - the node has the type jnt:user
   then
       Log "Creating welcome task for new user: " + node.getName()
       Create task "Welcome to Jahia!" with description "We are glad to have you in our platform." for user node.getName()
end

rule "A notification about new group member"
   when
       A new node is created
                - the node has the type jnt:member
       The node has a parent
   then
       Log "Notifying members of the group '" + parent.getParent().getName() + "' about new member '" + node.getName()
       Create task "New member in the group" with description "A new member was added to the group." for members of group parent.getParent().getName()
end

rule "A task has been created"
    when
        A new node is created
        - the node has the type jnt:task
        The node has a property assignee
    then
        Set the property state of the node with the value "active"
        Assign permissions "rw-" on the node to the user property.getNode().getName()
end

This module adds some new rule consequences that are not part of the default DSL:

Create task "Welcome to Jahia!" with description "We are glad to have you in our platform." for user node.getName()

And it also defines a global object of type Tasks named tasks:

global Tasks tasks

How to extend the domain specific language for my rules ?

It is also very simple. You only have to add a rules.dsl file in the WEB-INF/resources folder of your module. An example from the tasks module:

[consequence][]Create task "{title}" with description "{description}" for user {forUser}=tasks.createTask({forUser}, "{title}", "{description}", drools);
[consequence][]Create task "{title}" with description "{description}" for members of group {group}=tasks.createTaskForGroupMembers({group}, "{title}", "{description}", drools);

The extension made in a module will be available for all other modules, so if your modules rely on dsl from another module think of adding this module as a dependency of yours.

Here is an example of using tasks in your own module (say, Jahia RSS Feeds module):

In the pom.xml we have:

<Jahia-Depends>tasks</Jahia-Depends>

And the META-INF/rules.drl file content is:

###############################################################################
package org.jahia.modules.rss.rules

#list any import classes here.
import org.slf4j.Logger
import org.jahia.services.content.rules.*
import org.jahia.modules.tasks.rules.*

expander rules.dsl

#declare any global variables here
global User user
global Service service
global Logger logger
global Tasks tasks
###############################################################################

rule "Notification about new RSS feed"
   when
       A new node is created
                - the node has the type jnt:rss
        The node has a property url
   then
       Log "Notifying root about new RSS feed " + property.getStringValue()
       Create task "New RSS feed alert" with description "Read the newly added RSS feed, retrieved from '" + property.getStringValue() + "'" for user "root"
end

Note that the rule file must import the org.jahia.modules.tasks.rules.* package and have a definition for tasks global object, i.e.:

...

import org.jahia.modules.tasks.rules.*

...
global Tasks tasks
...

How to add your own global objects to your rule context for execution?

Define a Spring file in your module (META-INF/spring/module-name.xml). Define a bean of type ModuleGlobalObject. Example from the tasks module:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.jahia.services.content.rules.ModuleGlobalObject">
        <property name="globalRulesObject">
            <map>
                <entry key="tasks">
                    <bean class="org.jahia.modules.tasks.rules.Tasks" factory-method="getInstance">
                        <property name="taskService" ref="org.jahia.services.tasks.TaskService"/>
                    </bean>
                </entry>
            </map>
        </property>
    </bean>
</beans>

As for the DSL all modules will share the same set of global objects so be sure that the key, you use, is unique across all modules.

Changes from Jahia 6.5 / 6.6

As a new version of drools is now used, some changes may be required in the rules files you developed for Jahia 6.5 or 6.6.
The rules format did not change, but you may have to look after the following changes :
  • The KnowledgeHelper class has moved from org.drools.spi to org.drools.core.spi
  • The # sign for comments is deprecated, comments should now use java syntax : // or /* */

System events

In order to listen events sent via spring, you need to implement to ApplicationListener<ApplicationEvent> interface, replacing ApplicationEvent by the event class you want to listen to. See ApplicationListener

Module deployment events

TemplatePackageRedeployedEvent event is sent when a module is deployed on the server. ModuleDeployedOnSiteEvent is sent when a module is deployed to a site.

Login / logout events

Login and logout event can be activated, if your application need to listen to these events. You'll have to add fireLoginEvent and/or fireLogoutEvent properties in your jahia.properties file. An event will then be sent everytime a user logs in or out

JCR Events

You can extend the org.jahia.services.content.DefaultEventListener class to create a listener on JCR events. The event listener needs to be declared in the module spring file.

If the class is named org.jahia.community.CustomerDefaultEventListener, here is how to declare it in the module Spring file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean class="org.jahia.community.CustomDefaultEventListener">
        <property name="workspace" value="default"/>
    </bean>
</beans>

See JCR documentation for details on the different event types.