When integrating with certain applications, you might want to use jExperience’s personalization functionalities without serving full-page HTML content generated by Jahia. For example, you might want to retrieve a list of images that are stored in Jahia, and make sure that personalization or even test optimizations, such as A/B testing, are applied to the elements of the list. In this case, the list of images is stored in Jahia’s CMS and the personalization configuration is stored within the data that is managed by jExperience’s Jahia modules.
If we reduce the example from a list to a single variant element, this is actually very close to what an ad-server might look like, where you want to retrieve a single image for a specific user profile. This image will be delivered based on the criteria (such as segmentation, profile properties, location) that have been set up for personalization. We are in this scenario only interested in this (or these) resource, and not on a full-blown HTML page.
This is now possible in jExperience through the content personalization rendering API (aka personalization API). This API is primarily a content rendering API, and will not cover setting up or modifying existing personalization or test optimization components. These must simply be setup using jExperience’s UI. This should be acceptable in most use cases, where it is acceptable to use a back-end UI to edit/setup things and then use an API to access the result of the applied personalization.
Jahia provides two versions of this API: REST and GraphQL. We recommend using the GraphQL version as the REST version might be phased out in future versions.
Let's look at an example of how to use this.
Steps:
{ jcr { nodePerson: nodeByPath(path: "PATH_TO_YOUR_PERSONALIZED_NODE") { uuid name jexperience { personalizedVariant { name uuid path property(name: "text", language: "en") { value } } } } } }
{ jcr { nodeOpti: nodeByPath(path: "PATH_TO_YOUR_OPTIMIZED_NODE") { uuid name jexperience { optimizedVariant { name path uuid property(name: "text", language: "en") { value } } } } } }
In the above example you need the path to the personalized or optimized nodes. This is usually something in the form of "/sites/content/PATH" based on what you have setup in the Content & Media Manager module or in your site contents. The nodes you retrieve using these queries are the nodes that match the personalization or optimizations that were setup in jExperience's Personalization and Optimization tests UI.
You can learn more about the GraphQL API by using the GraphiQL Docs browser and looking at the "marketingFactory" field of the JCRNode type. You can also learn more about Jahia's GraphQL API here.
This rendering API output JSON structures, much in a similar way that Jahia’s REST API does, but it is a bit different in format and especially in functionality. It is also customizable since it uses Jahia views to perform its rendering, making it possible to adjust the final result output using custom modules (for example to customize the JSON output of a specific Jahia component).
Before going into the specific format of the JSON structures, let’s look at the URL format:
http://server:port/CONTEXT/PATH_TO_CONTENT.mf.json?QUERY_PARAMETERS
where:
Name | Value type | Default value | Optional | Description |
---|---|---|---|---|
hidden | boolean | false | Yes | If activated, the JSON output will contain content properties marked as hidden. |
pretty | boolean | false | Yes | If activated, the JSON output will be indented to make it easier to read. This is useful for debugging but shouldn’t be used in production. |
depth | integer | 1 | Yes | This parameter controls the depth of the tree that is generated in the JSON output. Usually a depth of 2 is suitable when rendering a list of elements to avoid having to request the elements in separate HTTP requests. |
wemSessionId | string | null | Yes | This parameter contains a valid jCustomer session ID (usually obtained from a previous request). If not specified, it will first look in the server session for an object named "wemSessionId" and if it cannot be found, a new sessionId is generated by jCustomer. In all cases, the jCustomer session ID will be part of the JSON result. |
wemProfileId | string | null | Yes | This parameter contains a valid jCustomer profile ID. If not specified, the API will try to find the value either in the context-profile-id cookie, or in a server-side session object named "wemProfileId" and setup by a previous request. |
wemPersonaId | string | null | Yes | This parameter contains a valid jCustomer Persona ID. If specified and valid, the persona ID is used to perform personalization and test optimizations against the persona instead of the current profile. The list of personas may be retrieved using jCustomer’s Persona API. |
Here is an example of such an URL :
http://localhost:8080/sites/digitall/home/texts.mf.json?hidden=true&pretty=true&depth=5
and here in an excerpt of the result (edited for readability):
{
"path": "/sites/digitall/home/texts",
"parentPath": "/sites/digitall/home",
"identifier": "42e3db2a-6394-4f8d-9c0e-7d8ade34ca4d",
"index": 1,
"depth": "5",
"nodename": "texts",
"properties": {
"j:nodename": "texts",
"j:fullpath": "/sites/digitall/home/texts",
"jcr:createdBy": "root",
},
"types": {
"primaryNodeType": "jnt:contentList",
},
"title": "texts",
"classes": "selectable",
"hasChildren": true,
"childNodes": [
{
"path": "/sites/digitall/home/texts/rich-text",
"parentPath": "/sites/digitall/home/texts",
"identifier": "9769d43a-bc76-4681-90e1-633b863a9562",
"index": 1,
"depth": "4",
"nodename": "rich-text",
"properties": {
"j:nodename": "rich-text",
"j:fullpath": "/sites/digitall/home/texts/rich-text",
"jcr:createdBy": "root",
"text": "<p><strong>Bold Text<\/strong><\/p>\n\n<p> <\/p>"
},
"types": {
"primaryNodeType": "jnt:bigText",
},
"title": "Bold Text",
"classes": "selectable",
"hasChildren": false
},
{
"path": "/sites/digitall/home/texts/experience-rich-text-2/rich-text-1",
"parentPath": "/sites/digitall/home/texts/experience-rich-text-2",
"identifier": "5ab49763-18b3-4613-973f-c056bf8a3e8b",
"index": 1,
"depth": "3",
"nodename": "rich-text-1",
"properties": {
"j:nodename": "rich-text-1",
"j:fullpath": "/sites/digitall/home/texts/experience-rich-text-2/rich-text-1",
"jcr:createdBy": "root",
"wem:tab": "",
"wem:jsonFilter": "",
"text": "<p>fallback<\/p>"
},
"types": {
"primaryNodeType": "jnt:bigText",
"mixinTypes": ["wemmix:editItem"],
"parentPrimaryNodeType": "wemnt:personalizedContent",
"parentMixinTypes": []
},
"title": "fallback",
"classes": "selectable",
"hasChildren": false
},
{
"path": "/sites/digitall/home/texts/optimization-variant-a/variant-c",
"parentPath": "/sites/digitall/home/texts/optimization-variant-a",
"identifier": "3ea48051-6584-4493-9a21-edf72d98b495",
"index": 1,
"depth": "3",
"nodename": "variant-c",
"properties": {
"j:nodename": "variant-c",
"j:fullpath": "/sites/digitall/home/texts/optimization-variant-a/variant-c",
"text": "<p>variant-c<\/p>"
},
"types": {
"primaryNodeType": "jnt:bigText",
"mixinTypes": ["wemmix:editItem"],
"parentPrimaryNodeType": "wemnt:optimizationTest",
"parentMixinTypes": []
},
"title": "variant-c",
"classes": "selectable",
"hasChildren": false
}
],
"wemSessionId": "1fc2bcb8-99fe-47d1-8432-213ff495d6ff",
"wemProfileId": "0e4768b6-b129-45a7-a312-19544d112b7b",
"wemPersonaId": ""
}
In this example, there are multiple things to note:
Whenever interacting with jCustomer’s API, Jahia’s API or jExperience’s Personalization API, you must ensure that you track and transfer any personalization identifiers that you may have retrieved as part of a request’s response.
For example, if you perform a context retrieval call using jCustomer’s REST API such as :
http://UNOMI_HOST:UNOMI_PORT/context.json?sessionId=MY_SESSION_ID
You will likely retrieve a profileId as a result of this context retrieval. If you provided a new session ID in MY_SESSION_ID, the session will be created in jCustomer with this identifier and you must use that for all subsequent calls to any APIs. For example, if you use the personalization REST API to render content, you will need to do something like this:
where MY_PROFILE_ID is the profile ID that was returned as a result of the request to the context.json servlet.
If instead you are using the Personalization GraphQL API, you can specify the sessionId and profileId as arguments to the marketingFactory field as in the following example:
{ jcr { nodePerson: nodeByPath(path: "PATH_TO_YOUR_PERSONALIZED_NODE") { uuid name jexperience(profileId: "MY_PROFILE_ID", sessionId: "MY_SESSION_ID" { personalizedVariant { name uuid path property(name: "text", language: "en") { value } } } } } }
If you do not specify a profileId or sessionId for the GraphQL arguments, that's entirely possible if you are properly managing cookies or passing them as query string parameters to the GraphQL endpoint. However if you are running into problems with session or profile tracking not working properly because of some middleware layer, it might be a good idea to use the field arguments to ensure proper tracking.
It is also recommended that you use HTTP clients that can either handle cookies properly or you will have to manually manage the cookies for proper Jahia server session tracking (for example, the JSESSIONID cookie). As a general rule, check whether the HTTP client you use supports cookies out-of-the-box. If not, you must parse the returned cookies and send them back when performing requests to both Jahia and jCustomer. The following table provides a list of the most important cookies and their usage:
Name | Origin | Type | Description |
---|---|---|---|
JSESSIONID | Jahia | string | This cookie is used to track server-side sessions inside Jahia. |
context-profile-id | Unomi | string | Used by jCustomer to track the profile ID. If a wemProfileId parameter is specified, it will override the cookie value but it would be best that they always be in sync. |
jExperience also provides integration with Jahia’s login servlet. For proper session and profile tracking to occur, you must add a request parameter to the /cms/login servlet when submitting a Jahia login. Here is an example:
http://JAHIA_HOST:JAHIA_PORT/CONTEXT/cms/login?wemSessionId=MY_SESSION_ID
The body of the request is in typical multipart/form-data format (default type for HTTP POST Form submissions) that contains the following fields:
Name | Origin | Type | Description |
---|---|---|---|
doLogin | boolean | Yes | Must be set to "true" for the login to be performed |
username | string | Yes | The user unique name |
password | string | Yes | The user’s password |
restMode | boolean | Yes | Must be set to "true" for this use case |
redirectActive | boolean | Yes | Must be set to "false" for this use case |
site | string | Yes | The site against which to login, which is used mostly to redirect back to the site after the login. In the case of integration with jExperience, this is also used to know in which site to retrieve the jCustomer configuration. |
The following sections describe typical personalization flows that application developers might want to integrate. Note that while the steps are quite detailed, most of them happen automatically when using the personalization API. For example, all interactions between Jahia and jCustomer are handled by jExperience. Other steps, such as the login or the session ID generation, must be explicitly integrated into the custom applications. Implicit operations are indicated with an [I] and explicit operations with an [E].
"Get personalized JSON" or "Get test optimization JSON" are not really different requests, they are both requests to the personalization API we have described previously
"DX" refers to the jExperience Jahia modules, not the Jahia server by itself.
"Unomi" refers to the jCustomer Context Server.
The following diagram describes the flow of a typical integration of personalization without authentication into a custom application:
Here is a more detail description of the steps:
The following diagram describes the flow of a typical integration of personalization with authentication into a custom application:
Here is a more detailed description of the steps:
The following diagram describes the flow of a typical integration of anonymous test optimization into a custom application:
Here is a more detailed description of the steps: