Magnolia 5.5 reached end of life on November 15, 2019. This branch is no longer supported, see End-of-life policy.
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.
Use the default endpoints for nodes
and properties
.
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 the REST API page.
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.
com.wordnik.swagger.annotations.*
, endpoints are automatically exposed in the Swagger tool for testing (see Swagger API explorer).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.
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:
Step 2 can go from very simple to very complex.
You also can write a template that renders HTML and assigns JSON to JavaScript variables:
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}" }
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.
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.
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
.
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Log in - Magnolia Bitbucket</title><script> window.WRM=window.WRM||{};window.WRM._unparsedData=window.WRM._unparsedData||{};window.WRM._unparsedErrors=window.WRM._unparsedErrors||{}; WRM._unparsedData["com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:user-keyboard-shortcuts-enabled.data"]="true"; WRM._unparsedData["com.atlassian.analytics.analytics-client:programmatic-analytics-init.programmatic-analytics-data-provider"]="false"; WRM._unparsedData["com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path.context-path"]="\u0022\u0022"; WRM._unparsedData["com.atlassian.plugins.atlassian-clientside-extensions-runtime:runtime.atlassianDevMode"]="false"; WRM._unparsedData["com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:date-format-preference.data"]="\u0022\u0022"; WRM._unparsedData["com.atlassian.bitbucket.server.feature-wrm-data:bitbucket.global.theme.data"]="false"; WRM._unparsedData["com.atlassian.analytics.analytics-client:policy-update-init.policy-update-data-provider"]="false"; WRM._unparsedData["com.atlassian.bitbucket.plugins.bitbucket-slack-server-integration-plugin:slack-link-error-resources.slack-link-error"]="{}"; WRM._unparsedData["com.atlassian.bitbucket.server.feature-wrm-data:user.time.zone.onboarding.data"]="true"; if(window.WRM._dataArrived)window.WRM._dataArrived();</script> <link rel="stylesheet" href="/s/c2e7fb6cb0fa3d99cfd1ed5573a5b732-CDN/-327823861/fdb5904/n1cn5w/a93e8b54a53a5453c42e8c7cfb35551a/_/download/contextbatch/css/_super/batch.css" data-wrm-key="_super" data-wrm-batch-type="context" media="all"> <link rel="stylesheet" href="/s/c3b4ddaba8d3dce9466950696444d252-CDN/-327823861/fdb5904/n1cn5w/80af30e6da65e29f7038393c9f70794e/_/download/contextbatch/css/bitbucket.page.login,-_super/batch.css" data-wrm-key="bitbucket.page.login,-_super" data-wrm-batch-type="context" media="all"> <link rel="stylesheet" href="/s/bf2916eb93ea7ba6a84c3be046c95434-CDN/-327823861/fdb5904/n1cn5w/d519805ac2904ed3b4db29856f637561/_/download/contextbatch/css/bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super/batch.css?slack-enabled=true" data-wrm-key="bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super" data-wrm-batch-type="context" media="all"> <script src="/s/6dcf9aa9352e12dc8dc168ef3f3260eb-CDN/-327823861/fdb5904/n1cn5w/a93e8b54a53a5453c42e8c7cfb35551a/_/download/contextbatch/js/_super/batch.js?locale=en-US" data-wrm-key="_super" data-wrm-batch-type="context" data-initially-rendered></script> <script src="/s/82aa06bf515f7a4d5c2ce5cd6bfc4c66-CDN/-327823861/fdb5904/n1cn5w/80af30e6da65e29f7038393c9f70794e/_/download/contextbatch/js/bitbucket.page.login,-_super/batch.js?locale=en-US" data-wrm-key="bitbucket.page.login,-_super" data-wrm-batch-type="context" data-initially-rendered></script> <script src="/s/da1cafd0ec4c647be47bd070d1c2e7b9-CDN/-327823861/fdb5904/n1cn5w/d519805ac2904ed3b4db29856f637561/_/download/contextbatch/js/bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super/batch.js?locale=en-US&slack-enabled=true" data-wrm-key="bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super" data-wrm-batch-type="context" data-initially-rendered></script> <meta name="application-name" content="Bitbucket"><link rel="shortcut icon" type="image/x-icon" href="/s/-327823861/fdb5904/n1cn5w/1.0/_/download/resources/com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:favicon/favicon.ico" /><link rel="search" href="https://git.magnolia-cms.com/plugins/servlet/opensearch-descriptor" type="application/opensearchdescription+xml" title="Bitbucket code search"/></head><body class="aui-page-focused aui-page-focused-small aui-page-size-small bitbucket-theme user-login"><script type="text/javascript">require('@bitbucket/internal/feature/theme').init();</script><ul id="assistive-skip-links" class="assistive"><li><a href="#content">Skip to content</a></li></ul><div id="page"><!-- start #header --><header id="header" role="banner"><section class="notifications"></section><nav class="aui-header aui-dropdown2-trigger-group" aria-label="site"><div class="aui-header-inner"><div class="aui-header-before"><button class=" aui-dropdown2-trigger app-switcher-trigger aui-dropdown2-trigger-arrowless" aria-controls="app-switcher" aria-haspopup="true" role="button" data-aui-trigger href="#app-switcher"><span class="aui-icon aui-icon-small aui-iconfont-appswitcher">Linked Applications</span></button><div id="app-switcher" class="aui-dropdown2 aui-style-default" role="menu" hidden data-is-user-admin="false" data-is-switcher="true"><div class="app-switcher-loading">Loading…</div></div></div><div class="aui-header-primary"><span id="logo" class="aui-header-logo bitbucket-header-logo"><a href="https://git.magnolia-cms.com"><img src="/s/-327823861/fdb5904/n1cn5w/1.0/_/download/resources/com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:bitbucket-logo/images/logo/bitbucket.svg" alt="Bitbucket"/></a></span><ul class="aui-nav"></ul></div><div class="aui-header-secondary"><ul class="aui-nav"><li class=" help-link"title="Help"><a class=" aui-dropdown2-trigger aui-dropdown2-trigger-arrowless" aria-controls="com.atlassian.bitbucket.server.bitbucket-server-web-fragments-help-menu" aria-haspopup="true" role="button" tabindex="0" data-aui-trigger><span class="aui-icon aui-icon-small aui-icon-small aui-iconfont-question-filled">Help</span></a><div id="com.atlassian.bitbucket.server.bitbucket-server-web-fragments-help-menu" class="aui-dropdown2 aui-style-default" role="menu" hidden data-aui-dom-container="body"><div class="aui-dropdown2-section help-items-section"><ul class="aui-list-truncate" role="presentation"><li role="presentation"><a href="https://docs.atlassian.com/bitbucketserver/docs-0814/Bitbucket+Data+Center+and+Server+documentation?utm_campaign=in-app-help&amp;utm_medium=in-app-help&amp;utm_source=stash" title="Go to the online documentation for Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:general-help">Online help</a></li><li role="presentation"><a href="https://www.atlassian.com/git?utm_campaign=learn-git&utm_medium=in-app-help&utm_source=stash" title="Learn about Git commands & workflows" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:learn-git">Learn Git</a></li><li role="presentation"><a href="/getting-started"class="getting-started-page-link" title="Overview of Bitbucket features" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:getting-started-page-help-link">Welcome to Bitbucket</a></li><li role="presentation"><a href="/#"class="keyboard-shortcut-link" title="Discover keyboard shortcuts in Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:keyboard-shortcuts-help-link">Keyboard shortcuts</a></li><li role="presentation"><a href="https://go.atlassian.com/bitbucket-server-whats-new?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" title="Learn about what's new in Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:whats-new-link">What's new</a></li><li role="presentation"><a href="https://go.atlassian.com/bitbucket-server-community?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" title="Explore the Atlassian community" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:community-link">Community</a></li><li role="presentation"><a href="/about" title="About Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:about">About</a></li></ul></div></div></li><li class=" alerts-menu"title="View system alerts"><a href="#alerts" id="alerts-trigger"class="alerts-menu" title="View system alerts" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:global-alerts-menu-item">Alerts</a></li></ul></div></div> <!-- End .aui-header-inner --></nav> <!-- End .aui-header --></header><!-- End #header --><!-- Start #content --><section id="content" role="main" tabindex="-1" data-timezone="-60" ><div class="aui-page-panel content-body" ><div class="aui-page-panel-inner"><main role="main" id="main" class="aui-page-panel-content" ><h2>Log in</h2><form class="aui top-label prevent-double-submit " action="/j_atl_security_check" method="post" accept-charset="UTF-8"><div class="field-group"><label for="j_username" >Username</label><input class="text long-field" type="text" id="j_username" name="j_username" autofocus accesskey="u"/></div><div class="field-group"><label for="j_password" >Password</label><input class="text long-field" type="password" id="j_password" name="j_password" accesskey="p"/></div><div class="aui-group"><fieldset class="group checkbox"><div class="checkbox"><input class="checkbox" type="checkbox" id="_atl_remember_me" name="_atl_remember_me" checked="checked" accesskey="r"/><label for="_atl_remember_me" >Keep me logged in</label></div></fieldset></div><div class="aui-group"><input name="next" type="hidden" value="/projects/DOCUMENTATION/repos/having-fun-with-yaml/raw/light-modules/module-a/renderers/json.yaml?at=master"/><input name="queryString" type="hidden" value="next=%2Fprojects%2FDOCUMENTATION%2Frepos%2Fhaving-fun-with-yaml%2Fraw%2Flight-modules%2Fmodule-a%2Frenderers%2Fjson.yaml%3Fat%3Dmaster"/><input class="aui-button aui-button-primary" type="submit" id="submit" name="submit" value="Log in" accesskey="s"/><a id="forgot" class="aui-button aui-button-link" name="forgot" href="/passwordreset" autocomplete="off" tabindex="0">Unable to access your account?</a></div></form></main></div></div></section><!-- End #content --><!-- Start #footer --><footer id="footer" role="contentinfo"><section class="notifications"></section><section class="footer-body"><ul><li data-key="footer.license.message">Git repository management for enterprise teams powered by <a href="https://www.atlassian.com/software/bitbucket/">Atlassian Bitbucket</a></li></ul><ul><li>Atlassian Bitbucket <span title="fdb5904e84ca72e8d62953007cf5cf6cfcdfebd8" id="product-version" data-commitid="fdb5904e84ca72e8d62953007cf5cf6cfcdfebd8" data-system-build-number="fdb5904"> v8.14.0</span></li><li data-key="footer.links.documentation"><a href="https://docs.atlassian.com/bitbucketserver/docs-0814/Bitbucket+Data+Center+and+Server+documentation?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Documentation</a></li><li data-key="footer.links.jac"><a href="https://jira.atlassian.com/browse/BSERV?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Request a feature</a></li><li data-key="footer.links.about"><a href="/about">About</a></li><li data-key="footer.links.contact.atlassian"><a href="https://www.atlassian.com/company/contact?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Contact Atlassian</a></li></ul><div id="footer-logo"><a href="https://www.atlassian.com/" target="_blank">Atlassian</a></div></section></footer><!-- End #footer --></div><script>require('bitbucket/internal/layout/base/base').onReady(null, "Magnolia Bitbucket" ); require('bitbucket/internal/widget/keyboard-shortcuts/keyboard-shortcuts').onReady();</script><script type="text/javascript">require('bitbucket/internal/page/login/login').onReady();</script></body></html>
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.
title: jsonprovider0815 templateScript: /ftl-json-provider/templates/pages/jsonprovider0815.ftl renderType: json #renderType: freemarker visible: true
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}
/getjson
. You can call it with the following URL: http://localhost:8080/magnoliaAuthor/getjson?workspace=tours
&path=/magnolia-travels/Kyoto
http://localhost:8080/magnoliaAuthor/getjson/tours /magnolia-travels/Kyoto
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 |
GET
(such as PUT
, POST
or DELETE
) cannot be implemented using just FreeMarker.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.
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.
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; }
I18nNodeWrapper
causes the values to be returned in their localized form.In a FreeMarker template, it is the wrapForI18n
method that makes sure the values are returned in their localized form.
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)]
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()}
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. 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.