Development best practices

  Written by The Jahia Team
 
Developers
   Estimated reading time:
7.0 7.1 7.2
This section provides a series of best practices to help developers produce standardized and error-free code. All entries in this section must be known to developers before any DX implementation. Should you find any information that belongs to that section but isn't part of it, please do let us know so that this guide gets updated.

The Best Practices guide applies to all DX7 versions and provides hands-on hints about syntax, caching, JCR management, performance optimization, error management and industrialization. 

Jahia terminology

Template SET

A Template Set module is a specific type of Jahia module, which provides page and content templates to a Jahia site. Templates can be used to do the following :

  • • Create specific areas to allow editors/contributors to add content
  • • Define common reusable elements on a page (example : navigation menu)

During page rendering, the content (which are nodes under a page) and the elements defined on the templates, are merged to provide the page.

Module

A module is a an OSGi bundle which contains resources (JSP, CSS, Javascript, Drools rules, Java code, …) and components. Modules are the primary way of adding new functionalities to Digital Experience Manager. Modules are like Jahia “plugins” that provide a way to add new features.

Component

A component (functional equivalent to “node type”) is a piece of content which can be instantiated within a template or a site.

Components can be associated to view(s) in order to be displayed (example : news component, navigation menu component, rich text component). They can have properties or even complex data structure, and they can be associated to Jahia advanced mechanisms (filters, actions, rules, …)

Jackrabbit definitions & content

Namespaces

The “Compact Namespace and Node type Definition” (CND) gives us a simple syntax for defining node types and declaring namespaces in a single file.

Declaration of a namespace and a node type example :

/* An example node type definition */ 
// The namespace declaration
<ns = 'http://namespace.com/ns'>

// Node type name
[ns:nodeType]

As the name of a node type is unique, namespaces prevents conflicts from occuring if you have several modules installed on your DX instance. We suggest to reproduce Jahia’s schema by having two namespaces by project :

  • One related to primary node types (example : <jnt='http://www.jahia.org/jahia/nt/1.0'>)
  • One related to mixin node types (example : <jmix='http://www.jahia.org/jahia/mix/1.0'>)

But you could also multiply namespaces for categorizing components or for avoiding conflicts between two developer teams working on a same DX instance.

Modifications of node types definitions

Modifications of the definitions.cnd file have to be made with caution, if content has already been created with this node type definitions it could lead to content integrity issues.

Type of modification Operation Comment
Namespace Creation Will not create problem
Namespace Deletion Should never be done. Instead of a deletion, stop using the previous namespace
Namespace Modification Should never be done. Instead of a modification, create a new namespace and stop using the previous one
Node type Creation Will not create problem
Node type Deletion

Should never be done before having deleted all the instantiated nodes of this type from the template/site

Instances of this node could be found and deleted using Jahia Tools/JCR Console. It is also possible to script (groovy) this operation

Node type Modification Renaming a node type is similar to perform a deletion of the previous node type, and creation of a new one
Property of a node type Creation Will not create problem
Property of a node type Deletion

Should never be done before having set the property to “null” on all the instantiated nodes, otherwise it will lead to publication issues.

Alternative possibility is to declare this property “hidden”, and cease using it.

Property of a node type Modification Should never be done if there is node instantiated with this property. If necessary, create a new property and refer to “Deletion” section above.

Components

Regarding components droppable/usable by contributors, Jahia provides a set of categories for classifying them (example : “Basic content”, “Advanced content”, “Form content”, …).

You can use existing categories or add new ones for your project. To do so, create a mixin inheriting of “jmix:droppableContent” :

[namespace:categoryName] > jmix:droppableContent mixin

Rendering scripts management

Properties manipulation

Displaying a property

When displaying a property which is not mandatory, it is highly recommended to first check if this property has a value, for avoiding to empty HTML elements :

<c:set var="textProp" value="${currentNode.properties.text}"/>
<c:if test="${not empty textProp}">
 <div>${textProp.string}</div>
</c:if>

Displaying properties from another node

When the view of a component should display properties from another node (example : list displaying children elements, display of a weakreference, …), the following code should be avoided :

<h2>${currentNode.properties['jcr:title'].string}</h2>
<h3>Company</h3>
${currentNode.properties.AnotherNode.node.properties['jcr:title'].string}

A direct presentation of another node's property will lead to cache issues.

In this case, the view should delegate the rendering of the other node's properties to it. If the other node has no view, a dedicated one should be added. Then in our first view, instead of displaying these properties directly, the other node will be responsible for the display of its own properties:

<h2>${currentNode.properties['jcr:title'].string}</h2>
<h3>Company</h3>
<template:module node="${currentNode.properties.AnotherNode.node}" view="hidden.nameOfTheView" />

Where the view “hidden.nameOfTheView” is defined like this :

${currentNode.properties['jcr:title'].string}

Another possibility is to continue displaying the other node’s property directly, but you then need to add a cache dependency toward the other node. In many cases this solution is more costly than delegating the rendering. If the other node is modified, you will have to enterily render the view of your component, instead of just the part related to the other node.

Title property

Several editorial components need a property title. Instead of defining a new string property, the best practice is to inherit from the mixin “mix:title”, which directly provides a property named “jcr:title” :

[nt:editorialComponent] > jnt:content, mix:title
- image (weakreference, picker[type='image'])

mix:title provides an extra an feature that allows automatic sync between the title and the system name of a node.  The system name will then have more meaning, while exploring the repository (or creating weakreferences), giving the editor a better experience than the default system name. If you wish to display the title coming from “mix:title” mixin, you could do it this way :

Efficient display of an image

When a component has a weakreference property of type image, instead of directly displaying the image, it is possible to use the native Jahia view (imageReference.jsp) this way :

<c:if test="${not empty imageProperty}">
<template:module node="${imageProperty.node}" editable="false"/>
</c:if>

It will :

  • Be faster for the developer
  • Handles automatically “alt” attribute of the image with the description field of this node (if editor/contributor filled it)
  • Automatically add width and height attributes, using the size of the image, for saving space for the image in the page (which will avoid resizing of the page during page loading)
  • Handles correctly cache for the image, without having to add a cache dependency

Drawback :

  • If new attributes have to be added (e.g CSS class), you will not be able to use the native view. In this case, you have to handle manually the display, or you can add a new view to imageReference

​Generating URLs

When generating a URL (example: displaying an internal link toward another node or a resource), it is important to use the c:url taglib . This mechanism allows DX to perform rewriting rules for preview/live mode, and it also enables Vanity URLs.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:url value="${currentNode.url}"/>

Externalization of properties

Each module has its own set of ".properties" files located under "src/main/resources/resources", and initialy named “nameOfTheModule.properties”.

These property files are used to save key/value parameters.

Key unicity

When resolving a property, DX searches for the key in all the property files of all modules of the platform. It is important to ensure unicity of your keys.

To do so, you can prefix your key by the name of the component using it :

nt_nameOfTheComponent.label.key = This is my label

Property/component name

By default, the name of your component and properties are technical keys and therefore aren't userfriendly. It is highly recommended to provide a label for your properties, to be displayed in the back-office while creating/modifying content.

In order to do so, you have to follow a naming convention for your labels :

  • For component, the key is : namespace_nameOfTheComponent
  • For a property, the key is : namespace_nameOfTheComponent.nameOfTheProperty

Example, for the following component :

[nt:editorialComponent] > jnt:content, mix:title
- image (weakreference, picker[type='image'])

Here are the keys to define for having friendlier labels :

nt_ editorialComponent = Editorial component
nt_ editorialComponent.image = Visual content

Labels in views (JSP)

Hardcoded labels should be avoided in JSPs for several reasons (no possible internationalization, no reutilisability, harder maintenance…). In this case, properties files could be used to provide labels.

To do so, first declare a new entry in your properties file, example :

nt_editorialComponent.label.author = Author

Then, inside your JSP, you can retrieve the value behind the key, using the taglib fmt :

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
…
<fmt:message key="nt_editorialComponent.label.author" />

Internationalization

There is no limitation in the number of .properties files  that can be created, One can have a .properties file for each supported language.

Create a new .properties file, in the same folder, for each language, by naming it this way :

  • nameOfTheModule.properties : default file used as a fallback or when the user has no locale specified
  • nameOfTheModule_de.properties : file used for German users
  • nameOfTheModule_en.properties : file used for English users
  • nameOfTheModule_fr.properties : file used for French users

Permissions

When using “root” user, permissions are never being checked. Please use another user for testing permissions on a site.

Cache management

CACHE

General rules

When a cache issue is raised on a view, developers tend to deactivate cache on this component. This could be legitimate in a very few cases, but normally the cache should never be totally deactivated.

When is it legit to deactive the cache on a view?

  • Displaying data from an external source (Webapp, RSS, external API, …) with a strong constraint on having no out-dated data
  • Displaying data on which you cannot add a cache dependency, with a strong constraint on having no out-dated data

Even in such cases, we strongly recommend not to deactivate cache, but instead, having a lower cache duration (a few minutes for instance).

Think about it: with 1000 hits per minutes on a component, even a 1 minute cache makes a huge difference - Only 0.1% of your hits are not cached, versus 100% with a 0 minute cache.
  • Try to make use of the DX per-fragment cache feature:
  • Make use of views dedicated to displaying only the not cacheable data: this view will have no cache
  • A view displaying the rest of the component, and performing a template:module on the first non-cacheable view

Example : given a component having a title, a description and displaying data coming from an external Webapp, we will create two views :

  • viewA : performs the call to the Webapp, and displays its data
  • viewB : displays title and description, and includes the viewA

View B :

<c:set var="title" value="${currentNode.properties['jcr:title']}"/>
<c:set var="description" value="${currentNode.properties['description']}"/>

<c:if test="${not empty title}">
 <h2>${title.string}</h2>
</c:if>

<c:if test="${not empty description}">
 <span class="description">${description.string}</span>
</c:if>

<%-- Including the view fetching and displaying external data --%>
<template:module node="${currentNode}" view="viewA"/>

Testing in live mode

In preview and edit/contribute modes, there is no cache in place. One common mistake is to review your components only in preview mode, avoiding cache issues.

It is important to test the rendering of your components directly in live mode.

Loggers

DX uses log4j logging framework. It is recommended to use loggers inside your Java classes (and  your Drools rules and Groovy scripts), logging at least informations which could be used for debugging.

By example, if a REST action is exposed and it requires a POST parameters, it could be interesting to log at least :

  • In debug level, input values
  • If a mandatory parameter is missing, in warning level, a message showing the missing parameter

Using a logger in a java class

How-to define a logger in your Java class :

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
…
private static final Logger LOG =
LoggerFactory.getLogger(NomDeVotreClasseJava.class);

Using the logger :

// Will display a log if log level is set to info or superior
LOG.info(“This is a info level log");

// Will display a log if log level is set to warning or superior
LOG.warn(“This is a warning level log");

// Will display a log if log level is set to debug or superior
LOG.debug(“This is a debug level log");

// Will display a log if log level is set to error or superior
LOG.error(“This is an error level log");

Using a logger in a Drools rule

When defining a new Drools rule in a .drl file, it is possible to directly use Java for creating and using a logger.

A logger set to info level is provided by default, a Drools consequence is provided by DX for this purpose :

[consequence][]Log {message}= logger.info({message});

Using this logger could then be done this way :

rule "Import file"
 salience 50
 when
   A new node is created
   The node has a parent
     - it has the extension type jnt:importDropBox
 then
   Import the node
   Log "Import file fired for node " + node.getPath()
end

Using a logger in a Groovy script

It is possible to execute Groovy scripts:

  • Directly in the Groovy Console : /tools/jcrConsole.jsp
  • By putting scripts directly in the following folder: /digital-factory-data/patches/groovy . By doing so, scripts will be executed at DX startup, when the JCR is ready but before allowing users to perform modifications on it, or directly when the script is dropped in the folder and the server is live.

When executing groovy scripts, a logger is automatically instantiated and can be used this way :

…
log.info(“Will be logged when executing my groovy script")
… 

Modify log levels for debugging

For debug purposes, sometimes it is interesting to change log levels for a class or a package. Instead of modifying the log4j.xml (/WEB-INF/etc/config/log4j.xml) it is possible to override the configuration directly from the tools administration.

This overriding is temporary, the configuration from the log4j.xml file will be reapplied at the next DX startup.

To modify loggers configuration from the tools, go to this page : /tools/log4jAdmin.jsp

Reusing code

As much as possible it is important to reuse code, for various reasons (time saving, improved maintainability, …).

This mutualization could and must be done on several levels.

Sharing code across modules

When components are being used on different projects (for instance, two different sites, such as an intranet and an extranet), we recommend creating a new “transversal” module defining these components.

It is especially true when exposing Actions, Java API or taglibs. This way, two sites could use this module, without having to have the other project’s custom components.

NB : it is always more efficient to split the code at the beginning of a project, rather than doing it once everything is implemented. 

Java resources

  • In order to share Java code across modules, some things need to be considered:
  • An export package of these classes in the pom.xml of the transversal module
  • In the other module, add a dependency toward the transversal module in its pom.xml

Example of an export package in the pom.xml of the module exposing Java classes :

<build>
    <plugins>
     …
         <plugin>
             <groupId>org.apache.felix</groupId>
             <artifactId>maven-bundle-plugin</artifactId>
             <extensions>true</extensions>
             <configuration>
                 <instructions>
                     <Jahia-Module-Type>module</Jahia-Module-Type>
                     <Jahia-Depends>default</Jahia-Depends>
                     <Export-Package>org.jahia....tag</Export-Package>
                 </instructions>
             </configuration>
         </plugin>
     …
    </plugins>
</build>

Example of dependency in the pom.xml of the other module :

<dependencies>
     …
     <dependency>
         <groupId>groupIDOfTransversalModule</groupId>
         <artifactId>artifactIDOfTransversalModule</artifactId>
         <version>2.5.0</version>
         <scope>provided</scope>
     </dependency>
     …
</dependencies>
…
<build>
     <plugins>
         …
         <plugin>
             <groupId>org.apache.felix</groupId>
             <artifactId>maven-bundle-plugin</artifactId>
             <extensions>true</extensions>
             <configuration>
                 <instructions>
                     <Jahia-Module-Type>module</Jahia-Module-Type>
                     <Jahia-Depends>default,artifactIDOfTransversalModule</Jahia-Depends>
                 </instructions>
             </configuration>
         </plugin>
         …
     </plugins>
</build>

Code sharing across JSPs

Shared code across views of a component

The easiest way to share common code across rendering scripts is to create hidden views that would hold only common code.

Then the template:include tag could be used. It also allows developers to pass parameters along:

…
<%-- Including the hidden view --%>
<template:include view="hidden.nameOfMyView">
<%-- We could send parameters to the view --%>
<template:param name="parameter1" value="${variable}"/>
</template:include>
…

Where the view “hidden.nameOfMyView” will display the code to be mutualized. If needed, you can get parameters this way :

${currentResource.moduleParams.parameter1}

Common code across several components

It is also possible to share views across several components by defining a common mixin associated with shared views.

[mix:commonBehavior] mixin

[nt:componentA] > jnt:content, mix:commonBehavior
- field1 (string, richtext) i18n mandatory
- field2 (weakreference, picker[type='image'])
- …

[nt:componentB] > jnt:content, mix:commonBehavior
- field1 (string) i18n mandatory 

Include of the view example :

<template:include view="nameOfTheViewOfCommonBehaviorMixin" />

Deployment best practices

Thanks to OSGi, Digital Experience Manager 7 supports hot deployment to allow a better SLA.

OSGi bundles are JAR files you have to deploy directly in digital-factory-data/modules. You can also deploy these JAR using the administration panel of DX.

When deploying a new version of a module, the behavior will be different depending on the operating mode of DX (development or production modes):

  • Development mode: the previous version of the module will be stopped, and the new one will be automatically started. 
  • Production mode: the new version will stay remain stopped until an administrator starts it manually.