Using Camel to send notifications and for integrations

November 14, 2023

Jahia uses Apache Camel an open-source integration framework to manage its interaction with many enterprise systems and network protocols. 

In Jahia, Apache Camel is mainly used for sending mail as notifications (workflow event, subscription, user registration, form submission) and also make the system react in an asynchronous way to some event on the platform (page viewed, log history of content in database, etc.).

Sending mail as notifications from Jahia

You can do that in two ways. The first and easiest way is to use the mail service from Jahia that will hide all Apache Camel calls from you. The mail service allows to send email with or without a dynamic mail template (written in any script languages supported on your platform (by default : groovy, velocity, freemarker)).

An example of code from the NewUser Action class that will send an email using a template on a new user registration :

public ActionResult doExecute(HttpServletRequest req, RenderContext renderContext, Resource resource,
                                  JCRSessionWrapper session, Map<String, List<String>> parameters, URLResolver urlResolver) throws Exception {

        String username = getParameter(parameters, "username");
        String password = getParameter(parameters, "password");
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return ActionResult.BAD_REQUEST;
        }

        Properties properties = new Properties();
        properties.put("j:email",parameters.get("desired_email").get(0));
        properties.put("j:firstName",parameters.get("desired_firstname").get(0));
        properties.put("j:lastName",parameters.get("desired_lastname").get(0));
        for (Map.Entry<String, List<String>> param : parameters.entrySet()) {
            if (param.getKey().startsWith("j:")) {
                String value = getParameter(parameters, param.getKey());
                if (value != null) {
                    properties.put(param.getKey(), value);
                }
            }
        }

        final JahiaUser user = userManagerService.createUser(username, password, properties);

        // Prepare mail to be sent :
        boolean toAdministratorMail = Boolean.valueOf(getParameter(parameters, "toAdministrator", "false"));
        String to = toAdministratorMail ? mailService.getSettings().getTo():getParameter(parameters, "to");
        String from = parameters.get("from")==null?mailService.getSettings().getFrom():getParameter(parameters, "from");
        String cc = parameters.get("cc")==null?null:getParameter(parameters, "cc");
        String bcc = parameters.get("bcc")==null?null:getParameter(parameters, "bcc");

        Map<String,Object> bindings = new HashMap<String,Object>();
        bindings.put("newUser",user);

        mailService.sendMessageWithTemplate(templatePath,bindings,to,from,cc,bcc,resource.getLocale(),"Jahia User Registration");

        return new ActionResult(HttpServletResponse.SC_ACCEPTED,parameters.get("userredirectpage").get(0), new JSONObject());
    }

The template code :

#* @vtlvariable name="newUser" type="org.jahia.services.usermanager.JahiaUser" *#
#* @vtlvariable name="bundle" type="java.util.ResourceBundle" *#
<html>
<body>
<h1>
    ${bundle.getString("user.has.registered")} ${newUser.getProperty("j:firstName")} ${newUser.getProperty("j:lastName")}</h1>
<p>${bundle.getString("user.login")} ${newUser.getName()}</p>
</body>
</html>

You can use the mail service from anywhere you want in your custom code (Actions, Tags, etc.). If you want to send some notifications in your custom rules, Jahia 6.6 provide a rules notifications service that you can use.

Sending mail as notifications from a rule

package org.jahia.services.content.rules

#list any import classes here.
import org.jahia.services.content.rules.*
import org.jahia.services.content.*
import javax.jcr.observation.Event
import org.slf4j.Logger

expander rules.dsl

#declare any global variables here
global User user
global Service service
global ImageService imageService
global ExtractionService extractionService
global RulesNotificationService notificationService
global Logger logger
global JCRStoreProvider provider

rule "notify user (create)"
    when
       A new node is created
        - the node has the type jnt:user
        - its name is not guest
        - its name is not root
    then
       Notify new user with mail template "/WEB-INF/notifications/templates/mail/newUser.vm"
end

Integrating with other frameworks

If needed you can also call directly Apache Camel from the mail service to succeed on integrating an external system with Jahia 6.6, as an example we will show you how we use the mail service from Jahia to override the mail management in JBPM with our own :

public class JBPMMailSession implements MailSession, DisposableBean {
    private MailServiceImpl mailService;
    private ProducerTemplate template;

    public JBPMMailSession() {
        mailService = (MailServiceImpl) SpringContextSingleton.getBean("MailService");
        template = mailService.getCamelContext().createProducerTemplate();
    }

    public void send(Collection<Message> emails) {
        if (mailService.isEnabled()) {
            for (Message email : emails) {
                CamelContext context = mailService.getCamelContext();
                MailEndpoint endpoint = (MailEndpoint) context.getEndpoint(mailService.getEndpointUri());
                Exchange exchange = endpoint.createExchange(email);
                template.send("seda:mailUsers?multipleConsumers=true",
                        exchange);
            }
        }
    }

    public void destroy() throws Exception {
        if (template != null) {
            template.stop();
        }
    }
}

Here we use the mail service to get access to Apache Camel Context, create a ProducerTemplate from Apache Camel Context and use it to send email after transformation of javax.mail.Message object to Exchange object from Apache Camel and send them through the asynchronous channel dedicated to mail.

Reacting asynchronously on events on the platform

Jahia redirect all its logs to Apache Camel through a log4j appender, this way you can define Apache Camel routes that will listen to this log channel and then filter it to only use a subtract of those logs or use them all like our Content History Service that store all logs in the Database.

For example there is two way to say that you want to send a notification on a specific type of content creation let us say that we will continue with the example of our new user registration.

We have already seen that there is a mail sent by the action to people defined inside the new user registration form component. Now we want also to send a less specific email to some administrators to know that there is a new user in the system. We just have to write a route that will listen to log channel then filter it to only react on user creation and then send an email.

Here is the route definition:

<route>
    <from uri="seda:logMsgs">
    <filter>
        <groovy>request.body.contains(~".*/users.* node created.*")</groovy>
        <to uri="seda:mailUsers?multipleConsumers=true"/>
    </filter>
</route>

Now you can use the MetricsLoggingService and configure it to make it act like you want and log what you need from your own code and then add a lot of system interaction in your enterprise with Apache Camel.

Using Camel to make Jahia interact with other systems

So far we have seen how to listen to Jahia 6.6 and send notifications or call external systems, let us do it the other way around.

For example we might want to allow our users to create content using mail :

First we create our route this one will checkmail on the inbox of a gmail account in imap, and send the message to our POJO bean myBean:

<route>
   <from uri="imaps://imap.gmail.com?username=YOUR_USERNAME@gmail.com&amp;password=YOUR_PASSWORD&amp;consumer.delay=60000"/>
   <to uri="myBean"/>
</route>
|
|
<bean id="myBean" class="org.jahia.prototype.MyBean">
   <property name="jcrTemplate" ref="jcrTemplate"/>
</bean>

Our bean will use the jcrTemplate to create JCR Session allowing to create content.

package org.jahia.prototype;

import org.apache.camel.Exchange;
import org.apache.camel.Handler;
import org.apache.camel.component.mail.MailMessage;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.jahia.api.Constants;
import org.jahia.services.content.*;

import javax.jcr.RepositoryException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.util.Locale;

public class MyBean {
    private transient static Logger logger = Logger.getLogger(MyBean.class);
    private JCRTemplate jcrTemplate;

    public void setJcrTemplate(JCRTemplate jcrTemplate) {
        this.jcrTemplate = jcrTemplate;
    }

    @Handler
    public void doSomething(Exchange exchange) {
        assert exchange.getIn() instanceof MailMessage;
        final Message mailMessage = ((MailMessage) exchange.getIn()).getMessage();
        try {
            final String subject = mailMessage.getSubject();
            MimeMultipart mailMessageContent = (MimeMultipart) mailMessage.getContent();
            final String content;
            if (mailMessageContent.getCount() > 1) {
                content = (String) mailMessageContent.getBodyPart(1).getContent();
            } else {
                content = (String) mailMessageContent.getBodyPart(0).getContent();
            }
            if (subject.startsWith("create news:")) {
                jcrTemplate.doExecuteWithSystemSession(null,Constants.EDIT_WORKSPACE, Locale.ENGLISH,new JCRCallback() {
                    public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                        JCRNodeWrapper node = session.getNode("/sites/mySite/contents/news");
                        String nodeTitle = StringUtils.substringAfter(subject, "news:").trim();
                        JCRNodeWrapper jcrNodeWrapper = node.addNode(JCRContentUtils.findAvailableNodeName(node,
                                JCRContentUtils.generateNodeName(nodeTitle, 30)), "jnt:news");
                        jcrNodeWrapper.setProperty("jcr:title", nodeTitle);
                        jcrNodeWrapper.setProperty("desc", content);
                        session.save();
                        return null;
                    }
                });
            }
        } catch (MessagingException e) {
            logger.error(e.getMessage(), e);
        } catch (RepositoryException e) {
            logger.error(e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
}

This bean will create a news in /sites/mySite/contents/news as soon as it detects a mail whose subject start with "create news:". To go further we should lookup the user using the From part of the mail message, and use the date of the mail to create the date of the news for example. We can also when we have found the user use its preferred language to enter the news in its language instead of Locale.ENGLISH.

This is just a simple example with Apache Camel you can do much more than that see Apache Camel Components page to have an idea of all the components you can use) but here you have an idea of how to use it inside Jahia 6.6, enjoy.