Page tree
Skip to end of metadata
Go to start of metadata

This page provides an overview of the different ways of retrieving JSON for JCR-based content in Magnolia as well as listing their respective advantages and disadvantages. 

Magnolia REST delivery endpoint

The delivery endpoint is a Magnolia endpoint introduced with version 2.0 of the Magnolia REST modules. This endpoint provides more flexibility compared to a standard JAX-RS endpoint, since it can be configured via YAML file.

It supports GET to read data and delivers one specific node for a given path, or a list of nodes specified by several parameters and filters. 

Example: This example searches nodes in the tours workspace that contain the keyword vietnam. It returns the results in descending order using the location property and restricts the limit to one.

curl -X GET 'http://localhost:8080/magnoliaAuthor/.rest/delivery/travelling/v1?q=vietnam&orderBy=location%20desc&limit=1' \
-u superuser:superuser

For more information and examples, see Delivery endpoint API.

Pros

  • Convenient and simple to use JSON structure.
  • Supports RESTful URLs.
  • Highly configurable via YAML file in a light module.
  • Provides a fine-grained security.

Cons

  • Read-only. 
  • Poor localization support. 
  • Cannot fetch personalized variants of pages or components (see personalization).

Magnolia REST nodes and properties endpoints

Magnolia endpoints for nodes and properties - available since version 1.0 of the Magnolia REST modules.

Example: Read JCR content of the /magnolia-travels/Kyoto tour from the Tours content app in the demo.

curl -H "Accept: application/json" http://localhost:8080/magnoliaAuthor/.rest/nodes/v1/tours/magnolia-travels/Kyoto  \
  -u superuser:superuser

For more information and examples, see their API pages:

Pros

  • Complete API to create, read, update and delete (CRUD) JCR data.
  • Supports RESTful URLs.
  • Provides a fine-grained security.

Cons

  • Can only fetch content from one workspace per single request.
  • Rigid and verbose JSON structure.
  • Poor localization support.
  • Cannot fetch personalized variants of pages or components (see personalization).

Custom Java endpoints

Create a custom REST endpoint depending on your needs.

The following example snippet is taken from the  CameraCollectionEndpoint .

    @Path("/allCameras")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Get all cameras.", notes = "Returns json for the list of all a camera objects.")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = STATUS_MESSAGE_OK, response = List.class),
            @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
            @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
            @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
    })
    public List<Camera> getAllCameras() {
        List<Camera> result = null;
        try {
            result = cameraCollectionPojoService.getAllCameras();
        } catch (RepositoryException e) {
            log.warn("Could not compute the list of all cameras.");
        }
        return result;
    }

To explore the complete example, clone the camera-collection module from git and read Camera Collection module on Magnolia Community Wiki.

Also read How to create a custom Java-based REST endpoint.

Pros

  • Highly configurable.
  • Provides a fine-grained security.
  • When using com.wordnik.swagger.annotations.* , endpoints are automatically exposed in the Swagger tool for testing (see Swagger API explorer).
  • Delivery of personalized variants of pages or components is possible (see personalization).

Cons

  • Requires Java skills.
  • Must be deployed in a Maven module.

FreeMarker template

You can implement a custom JSON provider in a FreeMarker template script. Use templating functions such as cmsfn , damfn and others to read data from JCR and render JSON in your preferred manner.

Approaches: A pure JSON provider or an HTML-mixed one

Pure JSON

You can write a FreeMarker template in a way that its response is pure JSON. This is what we consider as a template script based or FreeMarker based JSON provider. To go for this approach, do the following:

  1. Create a page template. A dialog is not required.
  2. Write FreeMarker code that renders JSON.
  3. Create a page with this new template. 
  4. Request the new page to get JSON in the response.

Step 2 can go from very simple to very complex. 

Mixed with HTML

You also can write a template that renders HTML and assigns JSON to JavaScript variables:

 Click here to expand to see an example which contains both HTML and JSON

<!DOCTYPE html>
<html xml:lang="${cmsfn.language()}" lang="${cmsfn.language()}">
  <head>
    [@cms.page /]
    <meta charset="utf-8" />
    [#assign pageNode = cmsfn.contentByPath("/magnolia-travels/Kyoto", "tours")]
    <script>
    var someJson = '${jsonfn.from(pageNode).add("@path", "name", "description").wrapForI18n().inline().print()}';
    </script>
  </head>
  <body class="mixed ${cmsfn.language()}">

    <div class="container ">
      <h1>Find a better title later on ;-)</h1>
    </div>
  </body>
</html>
See line 8 where JSON is assigned to a JavaScript variable.


Assembling JSON manually

Here is a simple example of how you can assemble JSON structure in FreeMarker:

[#assign pageNode = cmsfn.contentByPath("/magnolia-travels/Kyoto", "tours")]
{
"path" : "${pageNode.@path}",
"name" : "${pageNode.name}",
"description" : "${pageNode.description}"
}
When assembling JSON manually, you have maximal freedom to define the JSON structure, but this approach also requires a high coding effort.

The sample project camera-collection provides a more sophisticated FreeMarker-based JSON provider, see simple-json-provider.ftl and json-provider-utils.ftl on git.

Assembling JSON with jsonfn

jsonfn templating functions generate JSON from JCR nodes in any workspace. jsonfn helps you assemble JSON within a FreeMaker template script and provides powerful methods to generate JSON in a flexible way. It can resolve references to nodes of other workspaces and it also provides links to renditions of assets in one request. See resolving referenced nodes and renditions.

To achieve the same JSON as in Assembling JSON manually above, write the following code:

[#assign pageNode = cmsfn.contentByPath("/magnolia-travels/Kyoto", "tours")]
${jsonfn.from(pageNode).add("@path", "name", "description").print()}

jsonfn templating functions are not bundled with preconfigured Magnolia bundles or webapps.

Setting the proper content type of the response

If your FreeMarker template acts as a pure JSON provider, you may want to set the content type of the response to application/json . You cannot set the content type directly in the template definition. Instead you have to define a new renderer and reference this renderer in the template definition with the renderType property.

However, having the "proper" content type may not be required. In many use cases it is sufficient to use the FreeMarker renderType that responds with text/html .

Renderer configuration

/module-a/renderers/json.yaml
!inherit:freemarker
contentType: application/json

The above example requires the Magnolia specific !inherit directive available since Magnolia 5.5.6

Node name

Value

 modules


 rendering


 renderers


 json


 contentType

application/json

 extends

../freemarker

 type

json

Note that you can extend the default freemarker renderer.

Template definition

title: jsonprovider0815
templateScript: /ftl-json-provider/templates/pages/jsonprovider0815.ftl
renderType: json
#renderType: freemarker
visible: true

Turning a template-based JSON provider into a RESTful API with virtualURIMapping

You can create a template that accepts request parameters to specify the JSON content to be returned. In the example below, you can specify the workspace and the path of the node.

[#assign path = ctx.path!"undefined" /]
[#assign workspace = ctx.workspace!"tours" /]

[#assign response = '{ "error" : "Missing path parameter." }' /]

[#if path!="undefined"]
  [#assign contentNode = cmsfn.contentByPath(path, workspace)]
  [#assign response = jsonfn.from(contentNode).add("@path", "name", "description").wrapForI18n().print() /]
[/#if]

${response}
Create the page with the JSON-providing template at /getjson. You can call it with the following URL: 

http://localhost:8080/magnoliaAuthor/getjson?workspace=tours&path=/magnolia-travels/Kyoto

If you want to use a RESTful URL instead:

http://localhost:8080/magnoliaAuthor/getjson/tours /magnolia-travels/Kyoto

Add the following virtual URI mapping:

Node name

Value

 modules


 <your-module>


 virtualURIMapping


 getjson-fromWorkspace-byPath


 class

info.magnolia.cms.beans.config.RegexpVirtualURIMapping

 fromURI

/getjson/([0-9A-Z-a-z]+)([0-9A-Z-a-z-\/]+)

 toURI

forward:/getjson?workspace=$1&path=$2

Pros

  • Highly configurable.
    • Fully customizable when assembling JSON manually.
    • Decent customizable when using jsonfn.
  • No Java required.
  • Localization support.
  • Delivery of personalized variants of pages or components is possible (see personalization).

Cons

  • Security cannot be set per workspace and path, but only generally for the page that acts as the JSON provider.
  • Methods beyond GET (such as PUTPOST or DELETE ) cannot be implemented using just FreeMarker.

Getting localized JSON

If your content is localized, you can also get localized JSON output. See enabling multi-language content about how to create localized content.

Depending on the technique applied to get JSON, you may have to force getting the localized version or you will get a JSON version of the default locale.

With default endpoints for nodes and properties

Localized properties must be read as <property-name>_<LOCALE> . For example, description_de for the property named description . The default endpoints do not take the client's locale into account. When JSON is generated, the nodes are not wrapped with  I18nNodeWrapper . It is impossible to tell which language is default.

With custom JAVA endpoints

In a custom endpoint, you typically fetch  Node  objects (Java objects representing JCR nodes) that you transform into JSON later on. Make sure you wrap the node with  I18nNodeWrapper  before producing JSON. The following shows a method wrapping a node to make sure it is localized (copied from  TemplatingFunctions , which is the underlying Java class of cmsfn):

public Node wrapForI18n(Node content) {
    return content != null?new I18nNodeWrapper(content):null;
}
Here the I18nNodeWrapper causes the values to be returned in their localized form.

In a FreeMarker template

In a FreeMarker template, it is the wrapForI18n method that makes sure the values are returned in their localized form.

With manually assembled JSON

Use cmsfn.wrapForI18n to get a localized version of a content node:

[#assign pageNode_notLocalized = cmsfn.contentByPath("/travel/about", "website")]
[#assign pageNode_localized = cmsfn.wrapForI18n(pageNode_notLocalized)]

With jsonfn

When using jsonfn, add the wrapForI18n method before calling the print method.

[#assign pageNode = cmsfn.contentByPath('/magnolia-travels/Kyoto', 'tours') /]
${jsonfn.from(pageNode).add("title").wrapForI18n().print()}

The same-origin policy problem

According to the same-origin policy:

(...) a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin.

Wikipedia

Therefore, if your application resides within a domain that is different from the domain that hosts your data, the web browser will actively prevent accessing content stored under the host domain.

A viable solution for this is adding the so-called CORS filter into the filter chain and configuring it correctly to allow such requests to pass through. For more details please see the CORS filter documentation and Request processing and filters.