Architecture Overview

  Written by The Jahia Team
   Estimated reading time:

1 Administration (interfaces, app, communication CXS)

1.1 Concepts

Most of the concepts are detailed in the Unomi architecture overview documentation, this document adds a few more information on the usages made of these concepts in the Marketing Factory modules.

1.1.1 Goals

A goal is an object stored in CXS (not in Jahia), composed of one or two conditions the user’s actions need to match to fulfil it.

a page view (one condition).
    The user should have seen the page.
The form goal (two conditions)
    the user should have seen the page,
    the user should have submitted the target form.

The module part for the goals only uses the Goals service’s endpoint from CXS to display reports on them. The module displays the data from CXS using three directives:

  • A directive displays a small goal card with basic information (goal widget), visible in the site dashboard for each goals. More information is available on the goal’s report page.
  • A specific directive manages the report table (goal report table)
  • The last directive handles the timeline on top of list of goals and in the goal report view, this reusable directive gets the data from CXS and displays them in a charted timeline.

1.1.2 Campaign

A campaign is a set, delimited in time, of one or many goals. Campaign CXS endpoints are used to manage campaigns (create, edit, get reports, etc …). Part of the goals controllers’ code has been reused in the campaign interface to avoid duplicated code.

Campaign reuses goal application parts by using angular.extend that enable a goal controller to extent a campaign controller.
.controller('ManageCampaignGoalsListCtrl', ['$scope', '$route', '$location', '$modal', 'goalService', 'queryService', 'definitionService', 'i18nService', 'campaignService', 'campaignDetailed', '_', '$controller', '$q', 'DTOptionsBuilder', 'DTColumnBuilder', '$compile', 'genericModal',
    function ($scope, $route, $location, $modal, goalService, queryService, definitionService, i18nService, campaignService, campaignDetailed, _, $controller, $q, DTOptionsBuilder, DTColumnBuilder, $compile, genericModal) {
        angular.extend(this, $controller('ManageGoalsListCtrl', {
            $scope: $scope,
            $route: $route,
            $location: $location,
            $modal: $modal,
            goalService: goalService,
            queryService: queryService,
            definitionService: definitionService,
            i18nService: i18nService,
            currentCampaign: campaignDetailed.campaign,
            _: _

This definition of a “ManageCampaignGoalsListCtrl” extends a “ManageGoalsListCtrl”. By doing this the “ManageCampaignGoalsListCtrl” inherits of all the goal controller “ManageGoalsListCtrl” functions and behaviors unless the campaign controller overrides some by re-declaring them in its own controller.

1.1.3 Segments

A segment is a set of profiles that match a given condition. The segment’s interface enables to create, edit and remove segments.

The segment is recalculated each time a profile is updated in CXS. The modules allow you to manage them using CXS segments endpoints.

The condition builder is the directive used to create segment condition (see below).

1.1.4 Static lists

Static lists are lists of profiles. A service and end point, provided by CXS are used to manage static lists from the modules.

1.1.5 Profiles

Profile is the default container where all visitor’s data are stored regardless of any segmentation. Services are used to call CXS end points to manage profiles.

1.1.6 Persona

The persona panel uses the CXS end points to manage this container.

1.1.7 Form mapping

Form mapping are rules stored in CXS. When a form mapping is called, the corresponding rule from CXS is requested and transformed into an easier item to handle and display by the angularjs app. When the form mapping needs to be saved, the reverse operation can be done: the form mapping is transformed into a rule then pushed into CXS using rules end point.

1.1.8 Variant

A variant is the designation of a content inside an optimization test or a personalization. Optimization test and personalization can contain multiple variants and displays at least one to the end user.

1.1.9 Optimization test

An optimization is equivalent to an AB testing on contents. The interface allows to build an optimization test directly on the content. The configuration of an optimization is done in the edit engine, where a goal can be created for this optimization test.

An optimization test goal is a normal goal where the first condition has been replaced by “the user should have seen the optimized content”

When the goal is created and the optimization test published, a goal report table is displayed in the edit engine.

Like the campaign the optimization test angular app reuses part of the goal code, using extends, and reusing the goal report table directive.

1.1.10 Personalization

A personalization affects the display of a content based on the history and behavior of the user who is accessing it. Its configuration is done in the edit mode by creating conditions for all variants with the condition builder.

1.2 Site settings, angularjs app

Marketing Factory administration uses the site settings, each administration content is a simple empty jsp that declares the entry point for a given angular application.


<%@ taglib prefix="template" uri="" %>
<jsp:include page="../../commons/adminBaseImports.jsp"/>
<template:addResources type="javascript" resources="manage-profiles.js,manage-profiles-app.js"/>

<div ng-app="manageProfilesApp">
    <div ng-view></div>

All the JavaScripts needed for the applications, like moment, underscore or the angularjs plugins, are imported by the adminBaseImports.jsp.

Each application that contains a flow of execution uses angular-routes to navigate between steps.

Multiple view is composed of the following features : list, detail, creation view.

Please note that Base Settings has only one view.

1.2.1 The contextual information injected into apps

A Jahia render filter is used to declare some JavaScript context information, directly usable by the client side applications to create links to use the JCRRest API or to get the current locales, etc.

baseEdit: "/cms/edit/default/fr"
baseLive: "/cms/render/live/fr"
basePreview: "/cms/render/default/fr"
contextServerAdminUrl: "/modules/marketing-factory-core/proxy/ACMESPACE"
currentModuleURL: "/modules/marketing-factory-core"
currentSiteHomePagePath: "/sites/ACMESPACE/home"
currentSiteKey: "ACMESPACE"
currentSitePath: "/sites/ACMESPACE"
dmfScope: "ACMESPACE"
googleAPIKey: ""
i18nLabels: Object
jahiaUILocale: "en"
jcrRestAPIBase: "/modules/api/jcr/v1"
jcrRestAPIVersion: "v1"
localTemplatesPath: "/modules/marketing-factory-core/javascript/templates"

This filter also adds the good i18n version for angular and moment lib based on user locale.

1.2.2 Routes & Templates

The rest of the application is made using angularjs routes and static html templates. Each app has two associated JavaScript files.

the profile app used for “Marketing visitors” and “Marketing personas”, declares two JavaScript files:

manage-profiles-app.js: the one dedicated to the routes definitions, map a given URL to a specific template and a specific angularjs controller
manage-profiles.js: the one dedicated to the app it self, controllers, directives, only related to manage profiles app

1.3 The AngularJS applications architecture in detail:

1.3.1 The applications

  • Each interface is a dedicated app
  • An app can be routed, by angular-route
  • Each app has some dedicated controllers, directives, or even services
  • The wem-services
  • The file wem-services, centralize all the service that communicate with the CXS
angular.module('wemServices', ['underscore', 'ui-notification', 'i18n'])

    .config(['$httpProvider', 'NotificationProvider', function ($httpProvider, NotificationProvider) {
        /* Config $http service to handle errors only here */

    .service('notificationService', ['Notification', function (Notification) {
        /* Notification service to display, errors, info in the bottom right corner of the screen, inject notificationService to use */

    .service('loadingSpinnerService', function () {
        /* service to call to display a spinner loaded overlay in the page, the directive <loading-spinner></loading-spinner> need to in the page */

    .service('filteringService', function (_) {
        /* service used for filter element in list view, when an element is deleted, used because of the server delay to know that an element has just been removed */

    .service('goalService', ['$http', function ($http) {
        /* provides API to call Goal end points from ctx server, each function return HttpPromise, .success() or .error() are the common usage on the HttpPromise to get the results */

    .service('ruleService', ['$http', function ($http) {
        /* provides API to call Rules end points from ctx server */

    .service('segmentService', ['$http', 'jcrService', function ($http, jcrService) {
        /* provides API to call Segment end points from ctx server */

    .service('listService', ['$http', function ($http) {
        /* provides API to call Static list end points from ctx server */

    .service('queryService', ['$http', 'i18nService', 'notificationService', function ($http, i18nService, notificationService) {
        /* provides API to Query end points from ctx server */

    .service('definitionService', ['$http', function ($http) {
        /* provides API to call Definition end points from ctx server */

    .service('profileService', ['$http', function ($http) {
        /* provides API to call Profile end points from ctx server */

    .service('jcrService', ['$http', function ($http) {
        /* provides API to JCR rest API on current digital factory*/

    .service('campaignService', ['$http', 'i18nService', function ($http, i18nService) {
        /* provides API to call campaign end points from ctx server */

    .service('formMappingService', ['$http', 'i18nService', 'loadingSpinnerService', function ($http, i18nService, loadingSpinnerService) {
        /* provides API to create, edit form mapping using using ctx server end points */

    .service('internalSearchService', ['$http', 'i18nService', 'queryService', function ($http, i18nService, queryService) {
        /* provides API to manage internal searches using using ctx server end points */
    .service('geonamesService', ['$http', function ($http) {
        /* provides API to query geonames DB stored in CXS, using CXS end points */

If customized development is needed, the usage of external calls, or reusable service should be part of the wem-services angularjs module because we configure the http service in the wem-services module’s config block.

1.3.2 i18n

A service and a directive are provided to support i18n messages in angularjs templates. The service i18nService is available for injection or the directive message-key can be used to append the translation to the given html element. This code is available in the file i18n.js

Some attributes are optional to format the message. The translation can also be stored in an attribute of the element instead of the body of the html element.

We are using a tool that create the javascript lib corresponding to the resources bundles of the module, this is done at compilation by:


This allows the i18n to read directly this library to get translation.

1.3.3 Pickers

Directives allow a user to pick item from different sources, node picker or html tag picker. the pickers are in the file picker.js

  • node picker: allows a user to pick jcr node base on allowed types
  • html tag picker: allows a user to select the html tag from a Jahia live page. Rendering is a select box (dropdown list) presenting all the tags found on the page.
  • html tag visual picker: allows the user to select the html element from a Jahia live page, the page is rendered and the user can click on the element he wants to select directly
  • site language picker: allows a user to select a language

The most complicated one, the visual html picker, loads an iframe of the targeted page and allows the user to click on the desired element.

1.3.4 Condition builder

The condition builder enables the creation of marketing conditions based on event, session or profiles properties. Its directive, defined in condition.js, is recursive, all the template of the conditions types’ templates are stored in the /templates/condition/conditions folder.

This directive is used:

  • To create segment
  • For profiles advanced search
  • To create personalization

A hack has been used in the compile hook of the directive to be able to use it also in his own templates. the solution name is RecursionHelper in condition.js

1.1.1 Directives

Some simple directives are declared in wem-directives.js:

  • auto focus: auto focus the given element after page load
  • page head: fix scroll and page header
  • foldable input: is the inputs used on MF interfaces, detect the type of the input, date, text, number. Handle validation.
  • lettersOrNumbersOnly: the input may contain only letters or numbers
  • lowerThanNumber: the input number must be lower that the given limit
  • loadingSpinner: directive that come with the loading spinner service, to display/hide the spinner overlay
  • prefixModelByProperties: directive to fix the model, prefix the model by “properties.” to avoid issue of property not found on cxs side.

1.3.5 ManagerUtils

Static objects and utils functions are declared in a ManagerUtils namespace included in the file manager-utils.js

1.4 List views and tables:

The tables in Marketing Factory administration use datatable as well as server side paging, sorting and filtering.

1.5 Personalization and Optimization tests:

The content personalization is made on content in edit mode using action item. When created, the original content displayed is replaced by an optimization node that wraps the content. Then you can add more sub contents. This is handled by the action

A custom edit engine is used to display the interface related to the optimization, and declare a custom edit engine:

  • first is to declare the javascript that will handle the operations for the engine tab
<bean class="org.jahia.ajax.gwt.helper.ModuleGWTResources">
    <property name="javascriptResources">

At this stage, the wem-edit-personalization.js and wem-edit-optimizations.js will be loaded at the same time as the GWT in edit mode.

The wem-edit-optimizations.js contains the functions that handle the optimization custom engine operations like validation, save, etc:

function optimizationInit(data) {
    return $.parseHTML("<iframe id=\"optimizationFrame\" width=\"100%\" height=\"100%\" frameborder='0' src=\"" + jahiaGWTParameters.contextPath + jahiaGWTParameters.servletPath + "/editframe/default/" + jahiaGWTParameters.lang + data.getPath() + ".wem-edit-engine-opti.html\"/>")[0];

function optimizationDoValidate(validation) {
    var angularScope = customEditEngineGetAngularScope();
    if (angularScope) {

function optimizationDoSave(node) {
    var angularScope = customEditEngineGetAngularScope();
    if (angularScope) {

function optimizationLanguageChange(data) {
    // nothing to do

The optimizationInit appends an iframe on the current render (further called by GWT custom edit engine), using the template wem-edit-engine-opti. This template contains the angularjs application. This iframe’s behavior is similar to the marketing factory site settings.

The optimizationDoSave, called by GWT, retrieves the current angularjs controller to call a save() function in it. OptimizationDoValidate() operating patterns are similar. If the validation process returns an error, the save() function is not operated.

  • WARNING: the validation has to be synchronous since the save function will be called by GWT.
  • both save and validation are passing GWT objects to be used directly.

Finally, the custom engine bean declaration is needed for the mapping between the js functions and the custom engine tab :

<bean class="">
    <property name="key" value="wemnt:personalizedContent"/>
    <property name="engineTabs">
            <ref bean="Engine.Tab.Content"/>
            <ref bean="Engine.Tab.ListOrdering"/>
            <bean class="">
                <property name="id" value="dmfconditions"/>
                <property name="titleKey" value=""/>
                <property name="tabItem">
                    <bean class="org.jahia.ajax.gwt.client.widget.contentengine.CustomEditEngineTabItem">
                        <property name="onInitMethodName" value="personalizationConditionsInit"/>
                        <property name="onLanguageChangeMethodName" value="personalizationConditionsLanguageChange"/>
                        <property name="doSaveMethodName" value="personalizationConditionsDoSave"/>
                        <property name="doValidateMethodName" value="personalizationConditionsDoValidate"/>
                        <property name="handleCreate" value="true"/>
                        <property name="handleMultipleSelection" value="false"/>

Above is the definition of a type EngineConfiguration bean, that reuses the tabs Content and ListOrdering but declare a new EngineTab. This new engineTab declares a new TabItem of type CustomEditEngineTabItem.

The binding between the functions and the tabs’ actions is done as follows.

  • onInitMethodName -> personalizationConditionsInit
  • onLanguageChangeMethodName -> personalizationConditionsLanguageChange
  • doSaveMethodName -> personalizationConditionsDoSave
  • doValidateMethodName -> personalizationConditionsDoValidate

For more information about the mechanism behind, please refer to org.jahia.ajax.gwt.client.widget.contentengine.CustomEditEngineTabItem in DF core.

This allows the creation of a custom edit engine tab which then displays an iframe using a template. Communication between GWT and the inner application is done by javascript functions.

Optimization and personalization function in a similar way, each having a dedicated template, an embedded angularjs app and a js that connects GWT and the angular app.

1.6 Persona panel:

Persona are stored on CXS side, they are managed using the persona admin panel in the Marketing Factory admin.

The persona panel displayed in preview mode is added by a render filter ContextServerScriptFilter. It appends an iframe in the left panel that uses a “persona” template on the site in preview mode. Html customization and iframe display are done by the render filter using the wemPersonaPanel.groovy script.

The persona template contains an angularjs app. This app can be found in wem-persona-panel.js

Using the selected persona, this application directly interacts with wem.js from the parent window to mimic a user browsing the site by calling a dedicated function, loadPersonaContext, to init a persona session.

If the persona is modified in the persona panel in preview mode, the changes are not saved within CXS but in the browser session Storage. Their persistence only last the user session.

1.7 Credentials settings:

Communication setting with CXS are stored in a dedicated node context-server-settings. Password is hidden by a Node Decorator designed to avoid the password to be easily read.

1.8 Proxy to cxs:

All the Marketing Factory administration app have to go through a secured by proxy call the CXS to get information. There is no direct call to CXS from the client.

This allow us to secure the calls:

<bean id="proxyServlet" class="org.jahia.modules.marketingfactory.admin.ProxyServlet">
    <property name="contextServerSettingsService" ref="contextServerSettingsService"/>
    <property name="permissionMapping">
            <entry key="[GET]/cxs/segments/**" value="jcr:read_default"/>
            <entry key="[GET]/cxs/geonames/**" value="jcr:read_default"/>
            <entry key="[GET]/cxs/profiles/properties/**" value="jcr:read_default"/>
            <entry key="[GET]/cxs/profiles/personas/**" value="jcr:read_default"/>
            <entry key="[POST]/cxs/profiles/personas/search" value="canPersonalizeWithMarketingFactory"/>
            <entry key="[GET]/cxs/definitions/**" value="jcr:read_default"/>
            <entry key="[GET]/cxs/goals/**" value="jcr:read_default"/>
            <entry key="[POST]/cxs/goals/**/report" value="jcr:read_default"/>
            <entry key="[POST]/cxs/goals/**" value="jcr:write_default"/>
            <entry key="/**" value="canAccessMarketingFactory"/>

For example, the bean definition of the proxy allows us to define some end points that need some permissions to be called.

If customized development is needed, all the client side built URLs should use this JavaScript variable (accessible from every where in MF interfaces and angularjs apps):


equals to /modules/marketing-factory-core/proxy/ACMESPACE in case of ACMESPACE used as context server scope.

1.9 Additional plugins, frameworks:

The Angularjs application use some plugins or external JavaScript libs. All these libs are part of the module marketing-factory-angular.

  • angular 1.4.1
  • angular-chart & chart.js: used for the timelines and all the charts
  • angular-cookies: used to manage cookies, used by the persona panel
  • angular-datatables: used for all the tables in the applications
    • o we use server side pagination, sorting and filtering
    • o a custom pager has been developed for datatables:
      • ManagersUtils.initPagerProperties =function(DataTable, $scope, i18nService) {
        • o can be call from serverData fn
        • o Almost all datatables use a serverData fn to get the data and handle sortering and filtering
      • .withFnServerData(serverData);
  • angular-google-maps: used to draw and use google map API in angular app, used by persona panel and personalization (condition on geographical zone)
  • angular-md5: used to create MD5 hash, used by gravatar directive to build hash from mail
  • angular-moment & moment.js: used to manipulate dates, the usage of new Date() in JavaScript should be reduce as possible and replace by usage to moment() API
  • angular-resource: API to build rest client
  • angular-route: API to build routes based on urls, used for every app in site settings of MF
  • angular-simple-logger: dependency of angular-google-maps
  • angular-ui-notification: used to create bottom right notification, the notificationService is used in wem-services.
  • angular-underscore & underscore.js: provide many utils functions, to manipulate arrays, lists, json objects, doing map reduce, sorting, filtering, etc.
  • angular-ui & datepicker ui: used to build components based on bootstrap, like modals, date pickers, etc.
  • jquery 1.11.2: used only to be able to use datatables, it’s also used by angularjs internally

2 Live (communication CXS)

2.1 WEM.js

This javascript is included in the page by the ContextServerScriptFilter like the persona panel in preview. The wemContextServer.groovy includes the js in the html content, and generates the javascript that calls the init() function from the wem JavaScript object.

The init() function requests the context.js from the CXS by sending to it events and filters, if wem.js needs to resolve personalization. Else, the init() function sends events related to the current page view to CXS.

The wem object allows to register callbacks executed after the context.js is loaded from CXS. Thus listener is attached to a submitted form or a viewed video. This attachment is made possible by the storage of information on the page containing the form or the video within the context.

The wemContextServer.groovy also generates a JavaScript object named “digitalData”. This object contains information on the displayed resource. This information is reused to send the page view event into CXS.


contextServerPublicUrl: "" 
events: Array[1] 
    0: Object 
        eventType: "view" 
        scope: "ACMESPACE" 
        source: Object 
            itemId: "72fcc615-4581-46bf-83bd-68dc1e0221fd" 
            itemType: "site" 
            scope: "ACMESPACE" 
            __proto__: Object 
        target: Object 
            itemId: "7cdf8885-a3e1-4b39-a310-a203ce004056" 
            itemType: "page" 
            properties: Object 
                attributes: Object 
                category: Object 
                pageInfo: Object 
                    destinationURL: "http://localhost:8080/fr/sites/ACMESPACE/home.html" 
                    language: "fr" 
                    pageID: "7cdf8885-a3e1-4b39-a310-a203ce004056" 
                    pageName: "Accueil" 
                    pagePath: "/sites/ACMESPACE/home" 
                    referringURL: "http://localhost:8080/gwt/edit/27165459DD1E78D717BE51D2AA5A8D91.cache.html" 
                    __proto__: Object 
                __proto__: Object 
            scope: "ACMESPACE" 
            __proto__: Object 
        __proto__: Object 
    length: 1 
    __proto__: Array[0] 
loadCallbacks: Array[1] 
page: Object 
scope: "ACMESPACE" 
site: Object 
    siteInfo: Object 
        siteID: "72fcc615-4581-46bf-83bd-68dc1e0221fd" 

2.2 Context.js requested from CXS

The context.js is the JavaScript part coming from CXS, it contains the CXS JavaScript object present on a loaded page.

The CXS object contains trackedConditions, rules stored in CXS that are related to a specific source.

A user submits a form mapped with form mapping. This form is also target of a trackedConditions rule. The form submission sends data into CXS before they are sent to their main target.

CXS object contains also information on current user profile.

Functions are also provided by context.js and collect events. They are actually used by the wem.js in the context loaded callbacks to register listener that will send events to CXS on specific user operations. You can see that in the init() function of wem.js


collectEvent: (event, successCallBack, errorCallback)
collectEvents: (events, successCallBack, errorCallback)
createCORSRequest: (method, url)
createCookie: (name, value, days)
eraseCookie: (name)
filteringResults: null 
loadXMLDoc: (url, successCallBack)
merge: (obj1, obj2)
profileId: "278da1b4-87dd-44e8-9b2a-44f6e8a6250f" 
profileProperties: null 
profileSegments: null 
readCookie: (name)
sessionId: "b80c5ebe-97dd-4e29-9eab-d7198fd29452" 
sessionProperties: null 
trackedConditions: Array[2] 
    0: Object 
        parameterValues: Object 
            formId: "searchForm" 
            __proto__: Object 
        type: "formEventCondition" 
        __proto__: Object 
    1: Object 
        parameterValues: Object 
            formId: "advancedSearchForm" 
            __proto__: Object 
        type: "formEventCondition"

2.3 Login and file download events

A specific Event listener has been created in the modules to react on user login and file download.

The java class ContextServerApplicationListener handles those events and sends the correct event to the context server when it is needed:

if (applicationEvent instanceof LoginEngineAuthValveImpl.LoginEvent) {
    handleLoginEvent((LoginEngineAuthValveImpl.LoginEvent) applicationEvent);
} else if (applicationEvent instanceof FileServlet.FileDownloadEvent) {
    handleDownloadEvent((FileServlet.FileDownloadEvent) applicationEvent);

The login event is always sent to CXS, but the download event is sent only when the downloaded file has a specific mixin related to MF that tells us that this file needs to be watched.

2.4 Optimization test

The live view of optimization test calls a JavaScript function that uses the context loaded callbacks to send to CXS the content displayed to the user after the loading of the context.js.

In live a random content is displayed to the user, then the displayed content ID is stored in the sessionStorage and an event is sent to the CXS using the register callback. This allow us to build the report table for the given optimization based on the events sent to CXS.

In details:

There is a Rule stored in CXS that is triggered each time an optimization test is viewed for the first time by a visitor.

This rule will store the ID of the content viewed by the user in its profile properties.

That way we can build the report table based on the content see by the user.

For each session, users see the content displayed at the first time this feature is enabled by the storage of the content’s ID in the sessionStorage of the browser.

The optimization goal is similar to a typical goal except for the start condition replaced by a condition on the fact that the optimized content has been displayed – or not – to a profile.

2.5 Personalization

In live mode the personalization needs to resolve the filters to adapt the content’s display to the user. The registerfilter function from wem.js is used for this purpose.

The filters are resolved by the CXS when the context.js is requested. Filters’ results are part of the context.js response. Results are then sent to registerCallback that load the needed content that matches the results.

2.6 Optimization and personalization rules

Personalization and optimization operations depend on start date, end date, and specific rules.

We have some rules and jobs that run to detect if the optimization should end or not. This job asks CXS for the report to see if the maximum number of hits has been reached for example. If it is the case, an action is triggered to resolve the strategy of the optimization test, if the winner should be promoted or if we keep the default variant.

And to avoid orphaned jobs, we have rules on optimization deletion to remove the associated jobs

2.7 Sequence diagrams:

The user sees a page that contains:

  • An optimization test
  • A contact form that have a form mapping
  • A personalized content

The user logs in the application:

MF_handler is the event listener in the module: ContextServerApplicationListener