Getting Started
Introduction
From application's perspective, the main entry point to useCRISP services isResourceServiceBroker. You should always retrieve content or data via ResourceServiceBroker which always returns aResource instance as a result.
Finding Resources from Simple JSON API Backend
Suppose you have a simple REST API athttp://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 tohttp://localhost:8080/example-commerce/api/v1 inResourceResolver component configuration (seeExample 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 singletonResourceServiceBroker by using CrispHstServices.getDefaultResourceServiceBroker(HstServices.getComponentManager()).
After that, you can get content or data from an external backend by passing itsresource space name, "demoProductCatalogs" in this example (seeExample 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> <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 throughResource#isAnyChildContained() Java method orresource.anyChildContainer in an evaluation expression in templates.
- Resource#getChildren()#getCollection() returns a read-onlyjava.util.Collection object to be able to easily iterate child items in Freemarker templates.
- Resource#getValueMap() returns aValueMap object (extension ofjava.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 throughResource#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 specificResource object. The link generation topic is discussed in the next section.
Custom Link Generation for Resources
In the previous example, the followingCRISP 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 customResourceLinkResolver component in a ResourceResolver component. Please seeExample 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 specifyingresource space name,resource bean, then the tag library will invokeResourceServiceBroker to invoke the underlyingResourceLinkResolver for the specificResourceResolver component specified by theresource space name to create a URI link for the template. So, the underlying customResourceLinkResolver will ge given aresource 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 customResourceLinkResolver configuration inExample with Simple JSON REST API:
<bean> <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 findResource objects onResourceServiceBroker by passing a logicalresource space name and arelative resource path in the previous sections. However, sometimes, you might want to change therelative resource path dynamically based on some runtime variables because the backend REST API URL could vary in situations. For that reason,ResourceServiceBroker supportsPath 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, ifpathVars 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 fromResourceServiceBroker. You can simply replace the operation by "resolveResource(...)" and expect the resultResource object represents the single resource directly.
Supposehttp://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 HTTPPOST method instead of HTTPGET method (the default option) when resolving resources or binaries in their REST API implementations. Just as an example,ElasticSearch Search APIs provides HTTPPOST based API for a richer query capability.
As HTTP GET method is used in CRISP API by default, you should give amessage exchange hint (of org.onehippo.cms7.crisp.api.exchange.ExchangeHint type) to switch the HTTP method toPOST in that case. In each resource or binary resolving operations ofResourceServiceBroker, you can provide an additionalExchangeHint argument which should be created by usingorg.onehippo.cms7.crisp.api.exchange.ExchangeHintBuilder.
If you want to use other HTTP methods such asPUT orDELETE 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 HTTPPOST 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());Resolving Single Binary
If the target backend resource data is not representing aResource but representing just (downloadable) ad hoc (binary) data, you can resolve the target data to aorg.onehippo.cms7.crisp.api.resource.Binary object instead. Suppose you configured aResourceResolver 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 useResourceServiceBroker#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 invokeBinary#dispose() after use to clean up any temporary file or streams associated with the binary object. It depends onResourceResolver andBinary implementations, but it is always necessary to invoke the Binary#dispose() method after use for safety, to not keep garbage. The defaultBinary 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 resolvedBinary objects arenot cached in any circumstances unlikeResource objects. So, any caching related configuration wouldn't affectBinary content.
You can also provide an additionalExchangeHint argument which should be created by usingorg.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 HTTPPOST 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());// ...