Delivery API 0.9
This pages describes theDelivery API version 0.9, the default version inBloomreach Experience Manager 14.x.
The Delivery API version 0.9 isno longer supported as of Bloomreach Experience Manager 15.
In Bloomreach Experience Manager 14.3.0 and later,Delivery API v1.0 is available and can optionally beenabled.
In Bloomreach Experience Manager 15, Delivery API v1.0 is the only supported version.
Introduction
The Delivery API (formerly Page Model API) was designed to address the following two factors:
- Intuitive, built-in model contribution and aggregation JSON API
- Seamless integration with WCMS delivery tier and channel management features
A built-in JSON API should represent a generic resource representation, while the integration with a delivery tier framework should represent a dynamic page model, comprised of component representations and their content and domain-specific models, based on flexible page compositions through the Experience manager.
Also, it should be straightforward for developers to contribute any kind of model objects to the aggregated page representation in component implementations, without having to write boilerplate code by themselves.
The Delivery API provides an intuitive REST API endpoint. For example, for a channel served athttp://localhost:8080/site/, its Delivery API is available throughhttp://localhost:8080/site/resourceapi/. The JSON API resources represent intuitive models containing all the components, content items, menus, domain specific models, etc. in a generic and effective way. SPAs can consume the API to implement the delivery tier and fully integrate with channel management features as a result.
Developers can also use standard HST APIs to participate in contributing content items or domain-specific models to the aggregated page model representation. SeeModel Contribution API for details.
Delivery API 0.9
As explained inConfigure Delivery API, once you configure the Delivery API, it becomes available to SPAs automatically at the child mount path (e.g,/resourceapi), configured by the @hst:pagemodelapi property. Internally, HST Container automatically augments the mount with a child mount, the name of which is set to the@hst:pagemodelapi property value, for Delivery API. This auto-augmented child mount invokes the PageModelPipeline, which is fairly similar to the default website pipeline, except that it does not invoke the rendering phases (i.e., rendering FreeMarker/JSP templates). Instead it processes contributed model collection, page model aggregation and JSON serialization phases.
This enables SPAs to consume all the models included and aggregated for the page. For example, if the initial page, loading an SPA, is fromhttp://localhost:8080/site/myapp/ orhttp://localhost:8080/site/myapp/news/, then developers can figure out the API endpoint athttp://localhost:8080/site/myapp/resourceapi/ or http://localhost:8080/site/myapp/resourceapi/news/. As it uses the built-in HST Mount configurations, it is also possible to create links in the server-side code by using the standard org.hippoecm.hst.core.linking.HstLinkCreator API.
The Delivery API follows the core principles ofHATEOAS. An SPA enters a REST application through a simple HST-2 navigational URL. An aggregated page model is returned for the SPA to discover all the data within the resource representations to construct the page. Example payloads look like the following:
GET /site/resourceapi/ HTTP/1.1Host: localhost:8080Accept: application/json...
The JSON response is always of the following structure:
HTTP/1.1 200 OKAccess-Control-Allow-Origin: http://localhost:3000API-Version: 0.9Content-Type: application/json;charset=UTF-8Content-Length: ......{ "id": "r19", "_links": { "self": { "href": "http://localhost:8080/site/resourceapi" }, "site": { "href": "http://localhost:8080/site/" } }, "page": { ... }, "content": { ... }}The JSON response contains an HTTP body which represents anAggregated Page Model, described in the next section. On a high level, anAggregated Page Model contains its identifier, follow-up links, page representation containing components and domain specific models, and content models.
Aggregated Page Model
TheAggregated Page Model, which establishes the root level structure of Delivery API responses, can be depicted in a domain model as follows:

Each domain object is described below.
AggregatedPageModel
This defines the root level object in the Delivery API responses.
| Field | Type | Mandatory | Description |
|---|---|---|---|
| id | String | Yes | The reference namespace ID of the root page component. |
| _meta | Map<String,JSON> | No | Metadata map, containing pairs of String key and JSON value. |
| _links | Map<String,LinkModel> | No | Follow-up links, containing pairs of link name key andLinkModel value. |
| page | JSON | Yes | The page representation in JSON. This representation follows the same structure as the HST component configuration (ComponentWindowModel) for a page. |
| content | Map<String,JSON> | No | Content representation map, containing pairs of JSON Identifier key and content item representation in JSON. AnyHST Content Beans, contributed by eachHstComponent, for documents, folders, gallery images and assets are included in thiscontent field. |
Unlike other metadata fields, the page field and the content field represent dynamic page composition with components in a page and content item representations contributed by each component in a page.
Also, seeModel Contribution API for details on how each component can contribute content items tocontent field.
LinkModel
This defines a generic container of the follow-up link objects for a linkable resource representation.
| Field | Type | Mandatory | Description |
|---|---|---|---|
| href | String | Yes | The URI of this link. |
| type | String | No | The type of the link. e.g,internal,external orresource. internal means the link is in the same SPA page resources, and so SPA may retrieve the linked resource through AJAX (XHR) calls, without having to reload the whole SPA page. external links cannot be represented within the current SPA, meaning the link should be treated as a document request (normal URL) and should not be fetched as XHR request by the SPA. As of version 14.1.0,external links are fully qualified URLs. resource links are mostly for binary resources such as images and assets. As of version 14.1.0,resource links are fully qualified URLs. |
| rel | String | No | The name of the relationship that the linked resource has to the page from which it’s referenced. |
| title | String | No | The title of the link. |
ComponentWindowModel (HST Component Configuration)
This defines either a page component (which is a composite representation of HST Component Configurations) or a single descendant HstComponent resource representation.
| Field | Type | Mandatory | Description |
|---|---|---|---|
| id | String | Yes | The reference namespace ID of the component. |
| _meta | Map<String,JSON> | No | Metadata map, containing pairs of String key and JSON value. |
| _links | Map<String,LinkModel> | No | Follow-up links, containing pairs of link name key andLinkModel value. |
| name | String | Yes | HST Component configuration node name. |
| componentClass | String | Yes | HST Component class name. |
| type | String | Yes | HST Component class type. |
| label | String | No | HST Component catalog item's label. SeeChannel Editor Catalog for detail. |
| components | Array<ComponentWindowModel> | No | Array containing child copmonent representations, each of which contains data in the same structure of ComponentWindowModel, recursively. |
| models | Map<String,JSON> | No | Map of content, menu, domain-specific model representations, etc., containing string key and object value in JSON. Any models, that are contributed byHstComponent but not included in the top levelcontent field, are included in thismodels field in a component representation. |
Unlike other metadata fields, the components field and the models field represent dynamically composed component representations and content item reference, menu model representations or other domain-specific model representations.
SeeModel Contribution API for details on how each component can contribute menu, domain-specific models, etc. tomodels field.
Content vs. Models
As you may have noticed, there are two different kinds of model container fields. One is the top levelcontent field and the other ismodels in each component representation. Basically both include the models contributed by eachHstComponent.
The difference is, however, the top levelcontent field contains only WCMS content data for documents, folders, gallery images and assets, by default. The other models such as menu,HstURL,HstLink or other domain-specific models are included in themodels field of each component representation. When anHstComponent contributes a WCMS content model (for documents, folder, gallery images or assets), the real data of the content model will be included only in the top levelcontent field, while leaving a JSON Pointer reference as JSON String to the real JSON content data in themodels field of the component representation. Let's see the simplified example below.
{ // ...SNIP... "page":{ // ...SNIP... "components":[ { "id":"r19_r1", "name":"main", "type":"COMPONENT", "models":{ "document":{ "$ref":"/content/u895fb1b6410d497298946b6a06d2b361" }, "menu":{ "name":"main", "siteMenuItems":[ // ...SNIP.., ] } } } ], // ...SNIP... }, "content":{ "u895fb1b6410d497298946b6a06d2b361":{ "id":"895fb1b6-410d-4972-9894-6b6a06d2b361", "name":"banner1", "displayName":"banner1", "content":{ "name":"hap:content", "displayName":"hap:content", "value":"\n \n\n <p>Banner description</p>\n\n \n " }, "title":"Sample banner", "image":{ "$ref":"/content/ub89d576f680a4bbf9c272dced9da3d6c" }, "address":false, "localeString":"en" }, // ...SNIP... }}The first component contains two model objects. One is "document" and the other is "menu". Because the first one ("document") represents a WCMS content model, it is included in the top levelcontent field and referenced by a JSON Pointer reference property ("$ref"), whereas the second one ("menu") is embedded inside themodels field of the component itself.
This approach gives the following advantages:
- Even if anHST Content Bean object as a model is referred by multipleHstComponents in the same page, the object will not be serialized multiple times. Instead, the real content data will be serialized only once in the top levelcontent field, while each component representation has just a JSON Pointer reference as JSON String to the real content item. This makes JSON serializations a lot more effective.
- AnHST Content Bean object could have reference properties to other HST Content Bean objects. In that case, instead of nesting all the referred content data inside a content item representation, the referenced object property will be replaced by a JSON Pointer reference as JSON String as well and the real content data of the referred HST Content Bean object will be separately serialized into the top levelcontent field as a sibling. This makes the JSON structure a lot cleaner.
- The Delivery API module can also avoid any issues caused by circular references between HST Content Bean objects as a result.
Best Practice to Retrieve Model Objects in Application
Even if every HST Content Bean object as a model is referenced by a JSON Pointer reference ("$ref") by default, it is recommended to handle both cases in a generic way in your SPA applications because each case represents the same logical JSON Schema anyway whether it is referenced by a JSON Pointer reference or embedded with the real content.
For example, in JavaScript code, you can check whether or not the model object contains "$ref" field first. If found, you can look up the referenced model object. Otherwise, you can read and use the model object directly. Here's an example JavaScript code snippet:
import jsonpointer from 'jsonpointer';//...// suppose 'pageModel' is the root JSON object and 'models' represents the 'models' field of a component.let documentWrapper = models.document;let document;if (documentWrapper['$ref']) { // if it has a JSON Pointer reference, then find the real document content through the reference ID, using the JSON Pointer library. let documentRef = documentWrapper['$ref']; document = jsonpointer.get(pageModel, documentRef);} else { // otherwise, the document is embedded inside the models. document = documentWrapper;}// Now you can read the fields of 'document' object...// ...Check out theDemo Project and its source for more examples.
JSON Response Examples
Let's first take a look around a whole JSON response example from anAggregatedPageModel object below.
{ "id":"r19", "_links":{ "self":{ "href":"/resourceapi" }, "site":{ "href":"http://localhost:8080/site/myapp" } }, "page":{ "id":"r19", "name":"homepage", "componentClass":"org.hippoecm.hst.core.component.GenericHstComponent", "type":"COMPONENT", "components":[ { "id":"r19_r1", "name":"main", "componentClass":"org.hippoecm.hst.core.component.GenericHstComponent", "type":"COMPONENT", "components":[ { "id":"r19_r1_r1", "name":"container", "componentClass":"org.hippoecm.hst.pagecomposer.builtin.components.StandardContainerComponent", "type":"CONTAINER_COMPONENT", "label":"Homepage Main Container", "components":[ { "id":"r19_r1_r1_r1", "name":"banner", "componentClass":"org.onehippo.cms7.essentials.components.EssentialsDocumentComponent", "type":"CONTAINER_ITEM_COMPONENT", "label":"Banner", "models":{ "document":{ "$ref":"/content/u895fb1b6410d497298946b6a06d2b361" } }, "_meta":{ "paramsInfo":{ "document":"banners/banner1" }, "params":{ "com.example.cms7.targeting.TargetingParameterUtil.hide":"off", "document":"banners/banner1", "org.hippoecm.hst.core.component.template":"webfile:/freemarker/hstdefault/essentials-banner.ftl" } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r1_r1_r1" } } }, { "id":"r19_r1_r1_r2", "name":"banner1", "componentClass":"org.onehippo.cms7.essentials.components.EssentialsDocumentComponent", "type":"CONTAINER_ITEM_COMPONENT", "label":"Banner", "models":{ "document":{ "$ref":"/content/u9a3f1f5c530243c49bec584e810ffa2f" } }, "_meta":{ "paramsInfo":{ "document":"banners/banner2" }, "params":{ "com.example.cms7.targeting.TargetingParameterUtil.hide":"off", "document":"banners/banner2", "org.hippoecm.hst.core.component.template":"webfile:/freemarker/hstdefault/essentials-banner.ftl" } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r1_r1_r2" } } } ], "_meta":{ "params":{ } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r1_r1" } } } ], "_meta":{ "params":{ } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r1" } } }, { "id":"r19_r2", "name":"top-right", "componentClass":"org.hippoecm.hst.core.component.GenericHstComponent", "type":"COMPONENT", "_meta":{ "params":{ } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r2" } } }, { "id":"r19_r3", "name":"menu", "componentClass":"com.example.cms.components.HapMenuComponent", "type":"COMPONENT", "models":{ "menu":{ "name":"main", "selectSiteMenuItem":{ "depth":0, "repositoryBased":false, "name":"home", "expanded":true, "selected":true, "parameters":{ "css class":"home" }, "childMenuItems":[ ], "_links":{ "site":{ "href":"/site/myapp", "type":"internal" } } }, "siteMenuItems":[ { "depth":0, "repositoryBased":false, "name":"home", "expanded":true, "selected":true, "parameters":{ "css class":"home" }, "childMenuItems":[ ], "_links":{ "site":{ "href":"/site/myapp", "type":"internal" } } }, { "depth":0, "repositoryBased":false, "name":"news", "expanded":false, "selected":false, "parameters":{ "css class":"" }, "childMenuItems":[ ], "_links":{ "site":{ "href":"/site/myapp/news", "type":"internal" } } } ] } }, "_meta":{ "paramsInfo":{ "siteMenu":"main" }, "params":{ "selectedMenu":"on", "level":"1", "menu":"main" } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r3" } } } ], "_meta":{ "definitionId":"hst:pages/homepage", "params":{ } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19" } } }, "content":{ "u895fb1b6410d497298946b6a06d2b361":{ "id":"895fb1b6-410d-4972-9894-6b6a06d2b361", "_links":{ "site":{ "href":"/site/myapp", "type":"internal" } }, "name":"banner1", "displayName":"banner1", "content":{ "name":"hap:content", "displayName":"hap:content", "value":"\n \n\n <p>Banner description</p>\n\n \n " }, "title":"Sample banner", "image":{ "$ref":"/content/ub89d576f680a4bbf9c272dced9da3d6c" }, "address":false, "localeString":"en" }, "ub89d576f680a4bbf9c272dced9da3d6c":{ "id":"b89d576f-680a-4bbf-9c27-2dced9da3d6c", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner-1.png", "type":"resource" } }, "name":"banner-1.png", "displayName":"banner-1.png", "fileName":"banner-1.png", "description":"Description for banner-1.png", "original":{ "width":700, "height":250, "length":151972, "lastModified":1395331380000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner-1.png", "type":"resource" } } }, "thumbnail":{ "width":60, "height":21, "length":3125, "lastModified":1395331380000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/thumbnail/content/gallery/hap/banners/banner-1.png", "type":"resource" } } } }, "u9a3f1f5c530243c49bec584e810ffa2f":{ "id":"9a3f1f5c-5302-43c4-9bec-584e810ffa2f", "_links":{ "site":{ "href":"/site/myapp", "type":"internal" } }, "name":"banner2", "displayName":"banner2", "content":{ "name":"hap:content", "displayName":"hap:content", "value":"\n \n\n <p>Banner description</p>\n\n \n " }, "title":"Sample banner 2", "image":{ "$ref":"/content/udb5907cce507460eb54dde5d5c784e0a" }, "address":false, "localeString":"en" }, "udb5907cce507460eb54dde5d5c784e0a":{ "id":"db5907cc-e507-460e-b54d-de5d5c784e0a", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner2.png", "type":"resource" } }, "name":"banner2.png", "displayName":"banner2.png", "fileName":"banner2.png", "description":"Description for banner2.png", "original":{ "width":700, "height":250, "length":103656, "lastModified":1395504540000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner2.png", "type":"resource" } } }, "thumbnail":{ "width":60, "height":21, "length":2213, "lastModified":1395504540000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/thumbnail/content/gallery/hap/banners/banner2.png", "type":"resource" } } } } }}Note that the page field always has a components JSON Array and each item in the components JSON Array may contain its childcomponents JSON Array recursively. This makes sense because an HST page consists of a hiearchical collection of descendant components. SeeHST Component Configuration for detail.
If a component contributes a document, then the document content item model will be included as a reference in models field inside the component representation like the following fragment shown above:
{ "id":"r19_r1_r1_r1", "name":"banner", "componentClass":"org.onehippo.cms7.essentials.components.EssentialsDocumentComponent", "type":"CONTAINER_ITEM_COMPONENT", "label":"Banner", "models":{ "document":{ "$ref":"/content/u895fb1b6410d497298946b6a06d2b361" } }, "_meta":{ "paramsInfo":{ "document":"banners/banner1" }, "params":{ "com.example.cms7.targeting.TargetingParameterUtil.hide":"off", "document":"banners/banner1", "org.hippoecm.hst.core.component.template":"webfile:/freemarker/hstdefault/essentials-banner.ftl" } }, "_links":{ "componentRendering":{ "href":"/site/myapp/resourceapi?_hn:type=component-rendering&_hn:ref=r19_r1_r1_r1" } }}Note that the real content representation of the contributed document content item model is not included directly inside themodels field. Instead it contains only aJSON Pointer referece as JSON String ("$ref":"/content/u895fb1b6410d497298946b6a06d2b361"), by which you can find the real content item representation under thecontent top level field by the key. Also, a component representation may include metadata, links and other model representations such as menu.
Content item model representations are always included in the content top level field for effectiveness (e.g, to avoid multiple serializations for the same content item) like the following:
"content": { "u895fb1b6410d497298946b6a06d2b361":{ "id":"895fb1b6-410d-4972-9894-6b6a06d2b361", "_links":{ "site":{ "href":"/site/myapp", "type":"internal" } }, "name":"banner1", "displayName":"banner1", "content":{ "name":"hap:content", "displayName":"hap:content", "value":"\n \n\n <p>Banner description</p>\n\n \n " }, "title":"Sample banner", "image":{ "$ref":"/content/ub89d576f680a4bbf9c272dced9da3d6c" }, "address":false, "localeString":"en" }, "ub89d576f680a4bbf9c272dced9da3d6c":{ "id":"b89d576f-680a-4bbf-9c27-2dced9da3d6c", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner-1.png", "type":"resource" } }, "name":"banner-1.png", "displayName":"banner-1.png", "fileName":"banner-1.png", "description":"Description for banner-1.png", "original":{ "width":700, "height":250, "length":151972, "lastModified":1395331380000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/content/gallery/hap/banners/banner-1.png", "type":"resource" } } }, "thumbnail":{ "width":60, "height":21, "length":3125, "lastModified":1395331380000, "mimeType":"image/png", "_links":{ "site":{ "href":"http://localhost:8080/site/binaries/site/binaries/thumbnail/content/gallery/hap/banners/banner-1.png", "type":"resource" } } } }, // ...}Each content item representation also haslinks and other fields (such astitle,content and address) which are extracted from its mappedHST Content Bean class.
Also note that the referenced content item, theimage field in the above example, contains only a JSON Pointer reference as JSON String as well ("$ref":"/content/ub89d576f680a4bbf9c272dced9da3d6c"), so you can find the image content item representation separately.
Maximum Content Item Reference Depth Level
As explained above, if anHST Content Bean object is referred by an HstComponent, the content item will be included only once in the top levelcontent field. In this case, the reference depth level is 1, meaning that the content item is in the first reference depth level from the HstComponent.
Now, suppose the specific content item also has a reference to another content item, which makes the second referenced content item be in the reference depth level 2 from theHstComponent. Any referenced content item in the second or any deeper level will not be included in the top levelcontent field, by default, unless another HstComponent has a direct reference to it. So, the first content item representation will include only a JSON Pointer reference to the second content item. The maximum content item reference depth level is set to 1 in the system, by default.
How to Include up to N Level Content Items?
You can change the maximum content item reference depth level in two ways: (a) changing it per request, (b) changing the system default setting. (a) takes the precedence over (b).
In order to change it per request, add _maxreflevel request parameter like the following:
- http://localhost:8080/site/myapp/resourceapi/news/?_maxreflevel=2
The response will include referred content items in up to depth level 2.
In order to change the default maximum reference depth level, add the following in theHST-2 Container Configuration file (e.g,${catalina.base}/conf/hst.properties):
# The default maximum content item reference depth level#pagemodelapi.v09.defaultMaxContentReferenceLevel = 1pagemodelapi.v09.defaultMaxContentReferenceLevel = 3
The above configuration will change it from 1 to 3, which makes the response include content items in up to reference depth level 3.
Links: Internal vs. External
In thecontent section, the_links object may contain a site link which specifies atype. Thetype can be either "internal" or "external". "internal" means the page can be represented within the current SPA. This means the SPA can use an XHR request and do a partial page update instead of making a full page request. In the above example, we have:
"_links":{ "site":{ "href":"/site/myapp", "type":"internal" } },It means that the link, /site/myapp, can be represented within the current SPA. The SPA may opt to make an XHR call, at /resourceapi/site/myapp for example, instead of making a full page request.
Rich Content Rewriting
The top levelcontent field, containing pairs of JSON Identifier key and content item representation in JSON, are serialized versions of HST Content Beans. TheHippoDocumentBean instances may contain rich text fields, which are represented by HippoHtmlBean objects. When HippoHtmlBean objects are serialized to a JSON, the HTML content of theHippoHtmlBean objects are rewritten, to make sure that images and links are accessible and usable from the SPA. For example when a WCMS document contains a link to another document in a rich text field, the link is rewritten to more accessible and usable markups. See the following example:
<a href="/news/news1.html" data-type="internal"/>
The SPA retrieves thehref attribute which it can directly navigate to or it can do something smarter with it optionally: If thedata-type is "internal", then the link can be used as an XHR call (typically something like/resourceapi/news/news1.html) which updates part of the pages, instead of making a new page request. If, however, the data-type is "external", then the SPA should always do a full page request (never an XHR call) because the linked page cannot be displayed within the current SPA. Typically this happens when:
- There is a hybrid setup with SPA pages and the link represents a normal server side rendering page.
- The link belongs to a document that is part of a different channel which is not part of the SPA.
Summary
The built-in Delivery API provides an intuitive REST API endpoint for SPAs, ensuring seamless integration with WCMS. SPAs may consume the JSON API responses to construct pages, components, menus, etc. with referenced content item models or domain-specific models, contributed by each component and aggregated for the page, in their own SPA frameworks. The Delivery API provides a generic, extensible, and most effective aggregated model representation for easier SPA development support.