This article covers a Bloomreach Experience Manager version 13. There's an updated version available that covers our most recent release.

Getting Started

Introduction

From application's perspective, the main entry point to use CRISP services is ResourceServiceBroker. You should always retrieve content or data via ResourceServiceBroker which always returns a Resource instance as a result.

Finding Resources from Simple JSON API Backend

Suppose you have a simple REST API at http://localhost:8080/example-commerce/api/v1/products/ which returns product data like the following example in JSON array:

[
  {
    "SKU": "12345678901",
    "description": "MultiSync X123BT - 109.22 cm (43 \") , 1920 x 480, 16:4, 500 cd\/m\u00b2, 3000:1, 8 ms",
    "name": "CBA MultiSync X123BT",
    "extendedData": {
      "title": "CBA MultiSync X123BT",
      "type": "Link",
      "uri": "Awesome-HIC-Site\/-\/products\/12345678901",
      "description": "MultiSync X123BT - 109.22 cm (43 \") , 1920 x 480, 16:4, 500 cd\/m\u00b2, 3000:1, 8 ms"
    }
  },
  {
    "SKU": "12345678902",
    "description": "PA123W, 68.58 cm (27 \") LCD, 2560 x 1440, 6ms, 1000:1, 300cd\/m2, 1.073B",
    "name": "CBA PA123W",
    "extendedData": {
      "title": "CBA PA123W",
      "type": "Link",
      "uri": "Awesome-HIC-Site\/-\/products\/12345678902",
      "description": "PA123W, 68.58 cm (27 \") LCD, 2560 x 1440, 6ms, 1000:1, 300cd\/m2, 1.073B"
    }
  },
  //...
]

You can configure the baseUri property to http://localhost:8080/example-commerce/api/v1 in ResourceResolver component configuration (see Example with Simple JSON REST API as a reference). Then you can use "/products/" as a resource relative path when invoking ResourceServiceBroker like the following example:

ResourceServiceBroker broker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
Resource productCatalogs = broker.findResources("demoProductCatalogs", "/products/");
request.setAttribute("productCatalogs", productCatalogs);

Get a reference to the singleton ResourceServiceBroker by using CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager()).

After that, you can get content or data from an external backend by passing its resource space name, "demoProductCatalogs" in this example (see Example with Simple JSON REST API as a reference) and a relative resource path, "/products/" in this example.

The ResourceServiceBroker may invoke the backend REST service or it may retrieve a cached resource data for the caller. Anyway, it always returns a Resource object that allows to retrieve all the properties and traverse child Resource objects and properties very easily.

In the example code shown above, it sets the returned Resource object to "productCatalog" request attribute. So, that attribute can be accessed in a template (in Freemarker, JSP, etc) through the variable name, "productCatalog".

For example, the following example shows how you can retrieve properties and child resources in Freemarker templates:

<#assign crisp=JspTaglibs ["http://www.onehippo.org/jsp/hippo/crisp/tags"]>

<#-- SNIP -->

<#if productCatalogs?? && productCatalogs.anyChildContained>
  <article class="has-edit-button">
    <h3>Related Products</h3>
    <ul>
      <#list productCatalogs.children.collection as product>
        <#assign extendedData=product.valueMap['extendedData'] />
        <li>
          <@crisp.link var="productLink" resourceSpace='demoProductCatalogs' resource=product>
            <@crisp.variable name="preview" value="${hstRequestContext.preview?then('true', 'false')}" />
            <@crisp.variable name="name" value="${product.valueMap['name']}" />
          </@crisp.link>
          <a href="${productLink}">
            [${product.valueMap['SKU']!}] ${extendedData.valueMap['title']!}
          </a>
          (${product.getValue('extendedData/description')!})
        </li>
      </#list>
    </ul>
  </article>
</#if>
  • You can check if there's any child Resource objects through Resource#isAnyChildContained() Java method or resource.anyChildContainer in an evaluation expression in templates.
  • Resource#getChildren()#getCollection() returns a read-only java.util.Collection object to be able to easily iterate child items in Freemarker templates.
  • Resource#getValueMap() returns a ValueMap object (extension of java.util.Map interface) through which you can access all the properties or child resource objects very easily.
  • Also, Resource#getValue(String relPath) method supports a relative property path for convenience. For example, if a "product" resource object (backed by a JSON object) contains a child JSON object ("extendedData"), then you can access "description" property of "extendedData" resource object directly through Resource#getValue("extendedData/description") which is equivalent to ((Resource) Resource#getValueMap().get("extendedData")).getValueMap().get("description").
  • In the example shown above, <@crisp.link /> (or <crisp:link /> in JSP) tab library used to generate a URI link for the specific Resource object. The link generation topic is discussed in the next section.

 

Custom Link Generation for Resources

In the previous example, the following CRISP link tag library is used:

<@crisp.link var="productLink" resourceSpace='demoProductCatalogs' resource=product>
  <@crisp.variable name="preview" value="${hstRequestContext.preview?then('true', 'false')}" />
  <@crisp.variable name="name" value="${product.valueMap['name']}" />
</@crisp.link>

Basically, a link for a Resource cannot be generated without a configured custom ResourceLinkResolver component in a ResourceResolver component. Please see Example with Simple JSON REST API for an example configuration with a custom ResourceLinkResolver.

Anyway, the idea is that if you use <@crisp.link /> (or <crisp:link /> in JSP) tag library in a template with specifying resource space name, resource bean, then the tag library will invoke ResourceServiceBroker to invoke the underlying ResourceLinkResolver for the specific ResourceResolver component specified by the resource space name to create a URI link for the template. So, the underlying custom ResourceLinkResolver will ge given a resource space name anda Resource object to generate a URI link.

However, sometimes it might not be enough only with those, but it may require more "variables" to determine a right URI link for the Resource object. For that reason, <@crisp.link /> (or <crisp:link /> in JSP) tag library supports embedding <@crisp.variable /> (or <crisp:variable /> in JSP) tags to pass more variables to use in URI link generation process.

For example, you will be able to find the following Freemarker templating based custom ResourceLinkResolver configuration in Example with Simple JSON REST API:

<bean class="org.onehippo.cms7.crisp.core.resource.FreemarkerTemplateResourceLinkResolver">
  <property name="templateSource">
    <value>http://www.example.com/products/${(preview == "true")?then("staging", "current")}/sku/${resource.valueMap['SKU']!"unknown"}/overview.html</value>
  </property>
</bean>

The template may use two variables, "preview" and "name", passed by the <@crisp.variable /> (or <crisp:variable /> in JSP) tags inside <@crisp.link /> (or <crisp:link /> in JSP) tag in its expressions. But in this specific example, it used "preview" variable only for demonstration purpose.

Finding Resources with Path Variables to Expand Resource Relative Path

It was discussed that we can find Resource objects on ResourceServiceBroker by passing a logical resource space name and a relative resource path in the previous sections. However, sometimes, you might want to change the relative resource path dynamically based on some runtime variables because the backend REST API URL could vary in situations. For that reason, ResourceServiceBroker supports Path Variables on invocations, too. See the following example:

ResourceServiceBroker broker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
final Map<String, Object> pathVars = new HashMap<>();
// Note: Just as an example, let's try to find all the data by passing empty query string.
pathVars.put("fullTextSearchTerm", "");
Resource productCatalogs = resourceServiceBroker.findResources(RESOURCE_SPACE_DEMO_PRODUCT_CATALOG,
        "/products/?q={fullTextSearchTerm}", pathVars);
request.setAttribute("productCatalogs", productCatalogs);

In this example, it is assumed that the relative resource path cannot be statically determined every time, but could be determined at runtime based on some variables. e.g, "/products/q=hippo" where "hippo" can be determined at runtime by variables.

In this case, you can pass variables map on ResourceServiceBroker#findResources(String resourceSpace, String baseAbsPath, Map pathVariables) operation.

The resource relative path parameter will be expanded using the given path variables (pathVars), if there's any. For example, if pathVars looks like {"var1":"hello","var2":"world"} and the resource relative path parameter is ".../some/path/{var1}/{var2}/overview", then it is expanded to ".../some/path/hello/world/overview" when making a real request to the backend.

Resolving Single Resource

Resolving single resource is similar to finding multiple resources from ResourceServiceBroker. You can simply replace the operation by "resolveResource(...)" and expect the result Resource object represents the single resource directly.

Suppose http://localhost:8080/example-commerce/api/v1/products/sku/12345678901 returns the following JSON data:

{
  "SKU": "12345678901",
  "description": "MultiSync X123BT - 109.22 cm (43 \") , 1920 x 480, 16:4, 500 cd\/m\u00b2, 3000:1, 8 ms",
  "name": "CBA MultiSync X123BT",
  "extendedData": {
    "title": "CBA MultiSync X123BT",
    "type": "Link",
    "uri": "Awesome-HIC-Site\/-\/products\/12345678901",
    "description": "MultiSync X123BT - 109.22 cm (43 \") , 1920 x 480, 16:4, 500 cd\/m\u00b2, 3000:1, 8 ms"
  }
}

Then the following code will return a product resource representation backed by the JSON data.

ResourceServiceBroker broker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
Resource product = resourceServiceBroker.resolve("demoProductCatalogs", "/products/sku/12345678901");
assert "CBA MultiSync X123BT".equals(product.getValueMap().get("name"));

You can also expand the resource relative path by variables, too.

ResourceServiceBroker broker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
final Map<String, Object> pathVars = new HashMap<>();
pathVars.put("sku", "12345678901");
Resource product = resourceServiceBroker.resolve("demoProductCatalogs", "/products/sku/{sku}", pathVars);
assert "CBA MultiSync X123BT".equals(product.getValueMap().get("name"));

Using HTTP POST Method

Sometimes the target backend system allows only HTTP POST method instead of HTTP GET method (the default option) when resolving resources or binaries in their REST API implementations. Just as an example, ElasticSearch Search APIs provides HTTP POST based API for a richer query capability.

As HTTP GET method is used in CRISP API by default, you should give a message exchange hint (of org.onehippo.cms7.crisp.api.exchange.ExchangeHint type) to switch the HTTP method to POST in that case. In each resource or binary resolving operations of ResourceServiceBroker, you can provide an additional ExchangeHint argument which should be created by using org.onehippo.cms7.crisp.api.exchange.ExchangeHintBuilder.

Resource resolution methods such as #findResources(...) and #resolve(...) support only either GET (the default method) or POST method since resource resolutions are semantically consistent with GET method or practically applicable with POST method for many backends in reality nowadays.
If you want to use other HTTP methods such as PUT or DELETE for your Web/REST Services backends for some reason, please consider using #resolveBinary(...) method instead, which is explained in the next section.

The following example shows how you can suggest using HTTP POST method in message exchange, and it also shows how you can add or set custom HTTP request headers and/or request body in the request.

// You can build an ExchangeHint using ExchangeHintBuilder to change the HTTP method to POST with the request headers and request body.
Resource products = resourceServiceBroker.findResources("demoProductCatalogs", "/products/",
        ExchangeHintBuilder.create()
                .methodName("POST")
                .requestHeader("Content-Type", "application/json;charset=UTF-8")
                .requestBody("{ \"filterFieldName\": ... }")
                .build());
If you want to set a UTF-8 encoded (JSON) string for the requestBody, please make sure you pass a java.lang.String object in .requestBody(...) call. Otherwise, the Spring RestTemplate might not be able to convert it (a non-String object such as Jackson's JsonNode object) properly. Spring RestTemplate internally uses an org.springframework.http.converter.StringHttpMessageConverter object which reads the Content-Type request header (e.g, "application/json;charset=UTF-8") to find a proper character set to parse the request body. ISO-8859-1, by default if charset is not provided in the header value. Therefore, if you pass a non-String object (such as Jackson JsonNode object) in .requestBody(...) call, it will be converted by something else (such as org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) other than org.springframework.http.converter.StringHttpMessageConverter object, which can lead to a failure in parsing the request body mesage.

Resolving Single Binary

If the target backend resource data is not representing a Resource but representing just (downloadable) ad hoc (binary) data, you can resolve the target data to a org.onehippo.cms7.crisp.api.resource.Binary object instead. Suppose you configured a ResourceResolver for a DAM (Digital Asset Management) system, and you want to download binary resource data such as images or PDF files from it. In this case, you can use ResourceServiceBroker#resolveBinary(String resourceSpace, String absPath, ...) which returns a org.onehippo.cms7.crisp.api.resource.Binary object if resolved, like the following example:

// Suppose you get the ID of the asset to download from a Resource already.
String assetId = "1234567890";

Binary binary = null;
InputStream is = null;
BufferedInputStream bis = null;

try {
    final ResourceServiceBroker broker = CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager());
    final Map<String, Object> pathVars = new HashMap<>();
    pathVars.put("assetId", assetId);
    // Suppose you configured a ResourceResolver for a WebDAM REST API backend system.
    // It is to download the binary asset data from WebDAM REST API by appending the downloadable asset path.
    binary = broker.resolveBinary("webdamImages", "/assets/{assetId}/download", pathVars);
    // You can read the binary and do something as you need...
    is = binary.getInputStream();
    bis = new BufferedInputStream(is);
    // Do somethingn with the input stream on the binary data...
} finally {
    IOUtils.closeQuietly(bis);
    IOUtils.closeQuietly(is);
    // NOTE: Make sure to invoke Binary#dispose() to clean up any temporary file or streams assocated with the binary.
    if (binary != null) {
        binary.dispose();
    }
}

Please make sure to invoke Binary#dispose() after use to clean up any temporary file or streams associated with the binary object. It depends on ResourceResolver and Binary implementations, but it is always necessary to invoke the Binary#dispose() method after use for safety, to not keep garbage. The default Binary implementation stores the binary data into a temporary file under the hood, so it can have a chance to delete the temporary file when #dispose() method is invoked.

Any resolved Binary objects are not cached in any circumstances unlike Resource objects. So, any caching related configuration wouldn't affect Binary content.

You can also provide an additional ExchangeHint argument which should be created by using org.onehippo.cms7.crisp.api.exchange.ExchangeHintBuilder to use a different HTTP method or set any custom request headers. The following example shows how you can suggest using HTTP POST method in message exchange.

// You can build an ExchangeHint using ExchangeHintBuilder to change the HTTP method to POST with the request headers and request body.
binary = broker.resolveBinary("demoProductCatalogs", "/products/",
        ExchangeHintBuilder.create()
                .methodName("POST")
                .requestBody("{ \"filterFieldName\": ... }")
                .build());
// ...
In v2.2.1 or higher versions, unlike the resource resolution methods such as #findResources(...) and #resolve(...), you can use any other HTTP methods such as POST, PUT or DELETE as well when using #resolveBinary(...) method.

 

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?