Page Model API 1.0 - Bloomreach Experience - Open Source CMS

Page Model API 1.0

This feature is available since Bloomreach Experience Manager 14.3.0

This pages describes the Page Model API version 1.0. Bloomreach Experience Manager 14.x uses Page Model API version 0.9 by default. Page Model API version 1.0 can be enabled through configuration.

Introduction

The Page Model API was designed to address the following two factors:

  1. Intuitive, built-in model contribution and aggregation JSON API
  2. 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 Page Model JSON API provides an intuitive REST API endpoint. For example, for a channel served at http://localhost:8080/site/, its Page Model API is available through http://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. See Model Contribution API for details.

Page Model API Version 1.0

Once you configure the Page Model 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 the Page Model 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 from http://localhost:8080/site/myapp/ or http://localhost:8080/site/myapp/news/, then developers can figure out the API endpoint at http://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 Page Model API follows the core principles of HATEOAS. An SPA enters a REST application through a simple HST 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.1
Host: localhost:8080
Accept: application/json
...

The JSON response is always of the following structure:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
API-Version: 1.0
Content-Type: application/json;charset=UTF-8
Content-Length: ...
...
{
  "meta":{
    "visitor":{
       "id":"fc9240d1-31d7-4f0d-bbd1-5dd3e291628c",
       "header":"_visitor",
       "new":false
    },
    "visit":{
       "id":"0f706603-6045-4240-8138-39c62d92a669",
       "new":false
     },
     "version":"1.0",
     "branch":"master"
  },
  "links":{
     "self" : {
        "href" : "http://localhost/site/resourceapi",
        "type" : "external"
     },
     "site" : {
       "href" : "/",
        "type" : "internal"
      }
   },
   "channel":{
     "info":{
       "props":{
         "title" : "Test SPA"
        }
      }
   },
   "root":{
     "$ref":"/page/u1f0e3c3dc4c942bfb13130def8714ccd"
   },
   "document" : {
      "$ref" : "/page/u1ca07af9cdf54bdd9908f991245bb062"
   },
   "page":{
      "u1f0e3c3dc4c942bfb13130def8714ccd":{
          "type" : "component"
          ...
      },
      ...
      ...
      ...
      "u1ca07af9cdf54bdd9908f991245bb062":{
          "type" : "document"
          ...
      },
    }
}

The Page Model API v1.0 JSON response serializes objects of a set of typical classes (and their subclasses) flattened, in particular the classes HstComponent, HippoDocumentBean, CommonMenu, and Pagination. Implementation projects can mark objects to be flattened as well with the marker interface PageModelEntity. In a v0.9 response, the HstComponent tree (page hierarchy) would be serialized as a JSON hierarchy. Only content entries (HippoDocumentBean instances) were serialized in a flattened content section (see Page Model API v0.9 documentation). In v1.0, the HstComponent tree is serialized flattened, as is the SiteMenu (extends CommonMenu), which now is not embedded anymore in an HstComponent object in JSON but has a separate entry below /page.

Structure of Flattened JSON Objects

HstComponent Tree

For example, assume a component hierarchy with a parent and two child components. This gets serialized as follows:

  "root":{
     "$ref":"/page/uid1"
   },
   "page":{
      "uid1":{
         "id":"r3",
         "links":{
            ...
         },
         "meta":{
            ...
         },
         "name":"homepage",
         "type":"component",
         "componentClass":"org.hippoecm.hst.core.component.GenericHstComponent",
         "children":[
            {
               "$ref":"/page/uid2"
            },
            {
               "$ref":"/page/uid3"
            }
         ]
      },
      "uid2":{
         "type":"component"
         ...
      },
      "uid3":{
         "type":"component"
         ...
      }
}

In the example above, "id":"r3" is the root component and contains two child components.

HstComponent ParamsInfo and Parameters

An HstComponent configuration typically can have (targeted) parameters. Some of these parameters can be reflected through the ParametersInfo interface, some of them through Dynamic Component Parameters, and some are not reflected by either of those two. Parameters which are reflected by the ParametersInfo interface or Dynamic Component Parameters are serialized in the ParamsInfo section of an HstComponent, the residual parameters are serialized below params. For example:

"root":{
     "$ref":"/page/uid1"
   },
   "page":{
      "uid1":{
         "id":"r3",
         "links":{
            ...
         },
         "meta":{
            "paramsInfo" : {
               "pageSize" : "5"
            },
            "params" : { 
              "color" : "red"
            }
         },

HstComponent ParamsInfo for Referenced Documents

If the ParamsInfo contains a reference to another document, image set or asset (typically via the ParametersInfo interface method which has the @JcrPath annotation or via the equivalent in Dynamic Component Parameters), instead of a serialized value like:

"meta":{
     "paramsInfo" : {
        "relPath" : "banner/summer-sale"
        "absPath" : "/content/documents/myproject/banners/banner/summer-sale"
      }
}

The Page Model API v1.0 will replace it with something like :

"page" : {
  "uid1":{
    "meta":{
      "paramsInfo" : {
        "relPath" : "/page/uid6"
        "absPath" : "/page/uid6"
      }
    }
  },
  "uid6":{
     "type":"imageset"
     ...
  }
}

The Page Model API v1.0 will make sure that any referenced existing document, image set or asset from component properties will get serialized into the JSON response as well. This is important for Experience Page Documents, since those typically have a set of HstComponent items which reference other documents, banners and ecommerce products to be included.

HstComponent Model Objects

If an HST component also sets a model as request attribute for a menu or document (or image set or asset), that menu or document gets serialized flattened as well, and the component has a reference to that menu or document. If multiple components have a reference to the same document, they all get the same reference and the document gets serialized only once. The same goes for an image set or asset. For example, assume that in some HST component Java class, the following code is included:

public void doBeforeRender(final HstRequest request, final HstResponse response) {
    HstRequestContext requestContext = request.getRequestContext();
    final NewsBean newsBean = getNewsBean(.....)
        
    request.setModel("menu", requestContext.getHstSiteMenus().getSiteMenu("main"));
    request.setModel("document", requestContext.getContentBean());
    request.setModel("news", newsBean); 
    request.setModel("news-again", newsBean);
}

Then the serialized JSON is of the following structure:

   "root":{
     "$ref":"/page/uid1"
   },
   "document":{
     "$ref":"/page/uid3"
   },
   "page":{
      "uid1":{
         "id":"r3",
         "links":{
            "self":{
               "href":"https://localhost/site/resourceapi?_hn:type=component-rendering&_hn:ref=r3",
               "type":"external"
            }
         },
         "meta":{
            "definitionId":"hst:pages/homepage",
            "params":{
               
            }
         },
         "name":"homepage",
         "type":"component",
         "componentClass":"org.hippoecm.hst.core.component.GenericHstComponent",
         "children":[.......],
         "models" : {
            "menu" : {
              "$ref" : "/page/uid2"
             },{
            "document" : {
              "$ref" : "/page/uid3"
             },
             "news" : {
              "$ref" : "/page/uid4"
             },
             "news-again" : {
              "$ref" : "/page/uid4"
             },

          }
      },
      "uid2":{
         "type":"menu"
         ...
      }
      "uid3":{
         "type":"document"
         ...
      },
      "uid4":{
         "type":"document"
         ...
      }
}

Menu Serialized Format

The format of a serialized menu looks as follows:

"u93b2dee8ffda4a61804b4cf15ec6a285":{
  "type":"menu",
  "links":{
     
  },
  "meta":{
            
  },
  "data":{
    "name":"main",
    "siteMenuItems":[
      {
        "depth":0,
        "repositoryBased":false,
        "properties":{
          "hst:referencesitemapitem":"root",
          "hst:parameternames":[
            "css class"
          ],
          "hst:parametervalues":[
            "home"
          ]
        },
        "name":"home",
        "childMenuItems":[
           
        ],
        "parameters":{
          "css class":"home"
        },
        "links":{
          "site":{
            "href":"/",
            "type":"internal"
          }
        }
      }]
    }
  }
}

The menu is hierarchical and can have children in childMenuItems. The flattening is not done per menu item but only for an entire menu.

Document Serialized Format

A serialized document typically has a format like the example below. There is the type field, the links containing information about the URL and whether it is internal or external to the current SPA, meta which typically only contains document information for the Experience manager for the preview, and then there is the data section containing the actual contents of the document. If the HippoDocumentBean contains references to other HippoDocumentBean instances or ImageSet or Asset instances, they also get serialized flattened, as shown for the 'image' field below.

{
   "type":"document",
   "links":{
      "site":{
         "href":"/news/2015/08/2013-harvest.html",
         "type":"internal"
      }
   },
   "meta":{
      
   },
   "data":{
      "name":"2013-harvest",
      "displayName":"2013 harvest",
      "date":1440571980000,
      "source":"",
      "title":"2013 harvest",
      "introduction":"Lorem ipsum dolor sit amet",
      "author":"Alfred Anonymous",
      "image":{
         "$ref":"/page/ubeff458ee07a40658cad8d96b67d13ec"
      },
      "location":"Rome",
      "content":{
         "value":" <p>Lorem ipsum dolor sit amet</p> "
      },
      "localeString":"en",
      "id":"30092f4e-2ef7-4c72-86a5-8ce895908937"
   }
}

ImageSet Serialized Format

ImageSet instances are serialized as type 'imageset' and have all the different image variants serialized including the URL to retrieve them, for example:

    "udb02dde500984488a72c2a4fc6d51beb" : {
      "type" : "imageset",
      "links" : { },
      "meta" : { },
      "data" : {
        "name" : "picture.jpeg",
        "displayName" : "picture.jpeg",
        "description" : null,
        "original" : {
          "name" : "hippogallery:original",
          "displayName" : "hippogallery:original",
          "width" : -1,
          "height" : -1,
          "lastModified" : 1236880917884,
          "mimeType" : "image/jpeg",
          "filename" : "picture_original.jpeg",
          "size" : 168981,
          "links" : {
            "site" : {
              "href" : "http://localhost/site/binaries/unittestcontent/gallery/picture.jpeg",
              "type" : "resource"
            }
          }
        },
        "thumbnail" : {
          "name" : "hippogallery:thumbnail",
          "displayName" : "hippogallery:thumbnail",
          "width" : -1,
          "height" : -1,
          "lastModified" : 1236880917884,
          "mimeType" : "image/jpeg",
          "filename" : "picture_thumbnail.jpeg",
          "size" : 1490,
          "links" : {
            "site" : {
              "href" : "http://localhost/site/binaries/thumbnail/unittestcontent/gallery/picture.jpeg",
              "type" : "resource"
            }
          }
        },
        "fileName" : null,
        "localeString" : null,
        "id" : "db02dde5-0098-4488-a72c-2a4fc6d51beb"
      }
    }

Pagination Serialized Format

HstComponent instances that extend from DocumentQueryDynamicComponent have a search result-like model which is a org.hippoecm.hst.component.pagination.Pagination object, containing:

  1. A list of actual results for the current page

  2. How many results there are

  3. How many items are on a pages

  4. How many pages there are

  5. What the current page is 

Since the Pagination object extends from the PageModelEntity marker interface, it gets serialized flattened. Its structure looks as following, and the actual $ref in the 'items' are the included results in the Page Model API which also get serialized flattened:

    "uid7": {
      "offset": 0,
      "items": [
        {
          "$ref": "/page/u7467739e33b84a35b6b31659eb90fc67"
        },
        {
          "$ref": "/page/u5b49b58f903d4a91823a1b00fbabbf66"
        },
        {
          "$ref": "/page/uc28bf4536f834377a2ab219e6f71e72c"
        },
        {
          "$ref": "/page/u3046c7d1d14c42e9b13aa96d3c1ba819"
        },
        {
          "$ref": "/page/u9decc66350a340c3820cd6feaf06bab3"
        },
        {
          "$ref": "/page/ud94d39a9badb45419bf65d23e10f0aa3"
        }
      ],
      "total": 58,
      "first": {
        "number": 1,
        "links": {
          "site": {
            "href": "?r22_r1_r4:page=1&r22_r1_r4:limit=3",
            "type": "internal"
          },
          "self": {
            "href": "http://localhost/site/resourceapi?r22_r1_r4:page=1&r22_r1_r4:limit=3",
            "type": "external"
          }
        }
      },
      "previous": null,
      "current": {
        "number": 1,
        "links": {
          "site": {
            "href": "?r22_r1_r4:page=1&r22_r1_r4:limit=3",
            "type": "internal"
          },
          "self": {
            "href": "http://localhost/site/resourceapi?r22_r1_r4:page=1&r22_r1_r4:limit=3",
            "type": "external"
          }
        }
      },
      "next": {
        "number": 2,
        "links": {
          "site": {
            "href": "?r22_r1_r4:page=2&r22_r1_r4:limit=3",
            "type": "internal"
          },
          "self": {
            "href": "http://localhost/site/resourceapi?r22_r1_r4:page=2&r22_r1_r4:limit=3",
            "type": "external"
          }
        }
      },
      "last": {
        "number": 10,
        "links": {
          "site": {
            "href": "?r22_r1_r4:page=10&r22_r1_r4:limit=3",
            "type": "internal"
          },
          "self": {
            "href": "http://localhost/site/resourceapi?r22_r1_r4:page=10&r22_r1_r4:limit=3",
            "type": "external"
          }
        }
      },
      "pages": [
        {
          "number": 1,
          "links": {
            "site": {
              "href": "?r22_r1_r4:page=1&r22_r1_r4:limit=3",
              "type": "internal"
            },
            "self": {
              "href": "http://localhost/site/resourceapi?r22_r1_r4:page=1&r22_r1_r4:limit=3",
              "type": "external"
            }
          }
        },
        {
          "number": 2,
          "links": {
            "site": {
              "href": "?r22_r1_r4:page=2&r22_r1_r4:limit=3",
              "type": "internal"
            },
            "self": {
              "href": "http://localhost/site/resourceapi?r22_r1_r4:page=2&r22_r1_r4:limit=3",
              "type": "external"
            }
          }
        },
        {
          "number": 3,
          "links": {
            "site": {
              "href": "?r22_r1_r4:page=3&r22_r1_r4:limit=3",
              "type": "internal"
            },
            "self": {
              "href": "http://localhost/site/resourceapi?r22_r1_r4:page=3&r22_r1_r4:limit=3",
              "type": "external"
            }
          }
        }
      ],
      "size": 6,
      "enabled": true
    }

Specific Object Fields

type

Currently known types for a flattened object are:

For HstComponent

  • type = component

  • type = container

  • type = container-item

For HippoDocumentBean:

  • type = document

  • type = asset

  • type = imageset

and for CommonMenu:

  • type = menu

Based on the type of a flattened object, it is easier for SPAs and a graphQL layer to filter certain objects. For example, the frontend might not be interested in the 'menu' on consecutive requests. 

ctype (HstComponent)

If an HstComponent has a non-null value for getCType, then the 'ctype' field will be present in the serialized JSON for that component. The 'ctype' typically is useful for an SPA to 'know' what kind of component it is dealing with. In Page Model API v0.9, this information was often communicated by (mis-using) the hst:label. In Page Model API v1.0, combined with Dynamic Component Parameters, the ctype can be used as the contract between the SPA and the backend components.

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 the Demo Project and its source for more examples.

Whether the model is HST Content Bean or not, it is recommended to check whether the model is referenced by a JSON Pointer or embedded when reading data. Your SPA application will be more future-proof by doing that. In the future versions, more model objects, not just HST Content Bean objects, can be added to the top level content field for more efficienty, too.

Maximum Content Item Reference Depth Level

As explained above, if an HST Content Bean object is referred to by an HstComponent, the content item will be included flattened. 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 the HstComponent. Any referenced content item in the second or any deeper level will not be included in the top level content 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. If the maximum depth is reached, the reference will be serialized as the 'json pointer' from which the UUID of the referenced document can be deducted by the SPA and a separate request could be made to fetch only that content: Note that we do not yet have support for this latter but is identified as a feature to support

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 the HST-2 Container Configuration file (e.g, ${catalina.base}/conf/hst.properties):

# The default maximum content item reference depth level
#pagemodelapi.v09.defaultMaxContentReferenceLevel = 1
pagemodelapi.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, External, Resource and Unknown

In the content section, the links object may contain a site link which specifies a type. The type can be either "internal", "external", "resource" or "unknown". "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.

The value 'external' means the link does not represent a frontend SPA link that can be rendered as a partial page update.

The value "unknown" is for links to some resource that cannot be found (for example it is possible that some document or asset gets linked by a document for the current URL but that linked document cannot be fetched by a URL. The HST in that case returns a 'not-found-link', the Page Model API translates that in "unknown". An SPA implementation can decide what to do with the value.

Finally, the type "resource" is used for a reference to a binary from Bloomreach Experience Manager or an external binary storage.

Because an SPA can only handle internal links as partial updates, a link of type 'internal' is always a relative link, while the types 'external' and 'resource' always result in fully qualified URLs.

Impact on Links When a CDN Host is Configured

When a CDN host is configured, all links of type 'resource' will use the configured CDN host instead of the matched host for the URL! A configured CDN host only impacts links of type 'resource'.

Impact on Links When a Link URL Prefix is Configured

When a link URL prefix is configured, links of types 'resource' and 'external' will be prefixed with this URL (host) instead of the matched host. If both a link URL prefix and a CDN host are configured, links of type 'resource' will use the CDN host instead of the link URL prefix.

Rich Content Rewriting

Serialized HippoDocumentBean instances may contain rich text fields, which are represented by HippoHtmlBean objects. When HippoHtmlBean objects are serialized to a JSON, the HTML content of the HippoHtmlBean 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 the href attribute which it can directly navigate to or it can do something smarter with it optionally:

If the data-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 page, 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 in one of the following cases:

  • 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.

The data-type can also be "resource" indicating that the href points to a binary.

Summary

The built-in Page Model 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. Page Model API provides a generic, extensible, and most effective aggregated model representation for easier SPA development support.

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?