RESTful API Support - Plain JAX-RS Services - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 12. There's an updated version available that covers our most recent release.

08-01-2018

RESTful API Support - Plain JAX-RS Services

1. Introduction

HST-2 supports Plain JAX-RS Components integration. So developers can easily develop JAX-RS components for their own RESTful APIs and make use of full HST-2 content retrieval/management features in their JAX-RS components.

Hippo provides several mechanisms to expose REST APIs, read this overview to see which best fits your use case.

In order to use plain JAX-RS Services, you need to configure mount(s) in the repository configuration to use the JaxrsPlainRestPipeline, as well as enable that pipeline in the overriding Spring configurations.

Since Bloomreach Experience Manager 12.1, API documentation (/swagger.yaml or /swagger.json) in Swagger format can be generated automatically. This allows developers to use the Swagger UI to read the API specifications and try them out easily. 

2. How to enable a Plain RESTful Mount

To enable a Plain RESTful JAX-RS Services endpoint, you should configure a mount for that purpose first.

For example, you can configure a restservices mount like the following:

/restservices:
  jcr:primaryType: hst:mount
  hst:alias: restservices
  hst:ismapped: false
  hst:namedpipeline: JaxrsRestPlainPipeline
  hst:types: [rest]

In the above example we configured a /restservices mount to enable Plain JAX-RS RESTful services endpoint. The mount is processed via the JaxrsRestPlainPipeline which is provided by default by the HST-2 Container for the Plain RESTful services.

3. How to develop and configure Plain JAX-RS Components

Plain JAX-RS Components are just standard based JAX-RS Components. So, you can follow the best practices for building JAX-RS Components.

First, your JAX-RS resource component should be mapped by a path. In the example below, it is mapped to the " /products/" path.

package org.hippoecm.hst.demo.jaxrs.services;

@Path("/products/")
public class ProductPlainResource 
          extends org.hippoecm.hst.jaxrs.services.AbstractResource {

    @GET
    @Path("/{productType}/")
    public List<ProductRepresentation> getProductResources(
            @Context HttpServletRequest servletRequest,
            @Context HttpServletResponse servletResponse,
            @Context UriInfo uriInfo,
            @PathParam("productType") String productType) {

        // do nothing for now...
        return null;

    }

}   

The JaxrsRestPlainPipeline will select a target JAX-RS services endpoint through the configured mount path.

For example, when you navigate to http://localhost:8080/site/restservices/products/bike/, HST-2 Container will resolve the request to the " restservices" mount endpoint and its JaxrsRestPlainPipeline will delegate the discovery and invocation of the ProductPlainResource component to the JAX-RS engine by the remainder of the path, " /products/bike/", which will resolve " bike" as a path parameter named ' productType'. The JAX-RS engine then will invoke #getProductResources() method because the path is best matched with that method.

In order to register the JAX-RS resource component above, you must add the bean definition to the sourceList of customRestPlainResourceProviders like in the following example:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <!-- The following three imports will include pipeline configurations for
       both JaxrsRestPlainPipeline and JaxrsRestContentPipeline !!! -->
  <import resource="classpath:/org/hippoecm/hst/site/optional/jaxrs/
                    SpringComponentManager-rest-jackson.xml" />
  <import resource="classpath:/org/hippoecm/hst/site/optional/jaxrs/
                    SpringComponentManager-rest-plain-pipeline.xml" />
  <import resource="classpath:/org/hippoecm/hst/site/optional/jaxrs/
                    SpringComponentManager-rest-content-pipeline.xml" />

  <!-- Your custom JAX-RS REST Plain Resource Providers will be added into
       the following list !!! -->
  <bean id="customRestPlainResourceProviders"
        class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
      <list>
        <!-- Wrap your JAX-RS component by SingletonResourceProvider. -->
        <bean class=
             "org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider">
          <constructor-arg>
            <bean class=
             "org.hippoecm.hst.demo.jaxrs.services.ProductPlainResource" />
          </constructor-arg>
        </bean>
      </list>
    </property>
  </bean>


  <!-- Your custom JAX-RS REST Resource Providers will be added into the
       following list !!! -->
  <!-- The following sourceList is not used for Plain JAX-RS Services,
       but used for Content/Context Aware JAX-RS Services. -->
  <bean id="customRestContentResourceProviders"
        class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
      <list>
      </list>
    </property>
  </bean>

</beans>

You can configure and register more custom JAX-RS components when necessary.

4. Detailed Example

The above example JAX-RS component can be further implemented like in the following example:

package org.hippoecm.hst.demo.jaxrs.services;
@Path("/products/")
public class ProductPlainResource 
       extends org.hippoecm.hst.jaxrs.services.AbstractResource {

    @GET
    @Path("/{productType}/")
    public List<ProductRepresentation> getProductResources(
            @Context HttpServletRequest servletRequest,
            @Context HttpServletResponse servletResponse,
            @Context UriInfo uriInfo,
            @PathParam("productType") String productType) {

        List<ProductRepresentation> products =
            new ArrayList<ProductRepresentation>();

        try {
            HstRequestContext requestContext = RequestContextProvider.get();
            HstQueryManager hstQueryManager =
                getHstQueryManager(requestContext.getSession(),
                                requestContext);

            String mountContentPath =
                requestContext.getResolvedMount().getMount()
                                                    .getContentPath();
            Node mountContentNode =
                requestContext.getSession().getRootNode()
                  .getNode(PathUtils.normalizePath(mountContentPath));

            HstQuery hstQuery =
                hstQueryManager.createQuery(mountContentNode,
                                            ProductBean.class);
            Filter filter = hstQuery.createFilter();
            filter.addEqualTo("demosite:product", productType);
            hstQuery.setFilter(filter);
            hstQuery.addOrderByDescending("demosite:price");
            hstQuery.setLimit(10);

            HstQueryResult result = hstQuery.execute();
            HippoBeanIterator iterator = result.getHippoBeans();

            while (iterator.hasNext()) {
                ProductBean productBean =
                    (ProductBean) iterator.nextHippoBean();

                if (productBean != null) {
                    ProductRepresentation productRep =
                        new ProductRepresentation().represent(productBean);
                    productRep.addLink(getNodeLink(requestContext,
                                       productBean));
                    productRep.addLink(getSiteLink(requestContext,
                                                   productBean));
                    products.add(productRep);
                }
            }

        } catch (Exception e) {
            throw new WebApplicationException(e);
        }

        return products;
    }
}

In a client-side HTML page or script you now can invoke the resource service URL. Here's a simple HTML link example invoking the resource service:

<p>  Product :
  <a href=
        '<hst:link path="/restservices/products/${document.product}/" />'
     target='_blank'
     title=
        'Click the left link to see all products of the same product
         type in XML generated by a Plain JAX-RS Service.'>
     ${document.product}</a>
</p>

Unlike Content/Context Aware JAX-RS Services, when using Plain JAX-RS Services, you will need to know how the URL should be like. In the example above, it used <hst:link/> tag with predefined URL paths.

5. Using Matrix Parameters

Plain JAX-RS Services can use matrix parameters by using @MatrixParam annotations.

However, please note that the current JAX-RS runtime library, Apache CXF, adopted by HST-2, does not support matrix parameters in multi path segments.

For example, if your RESTful URL is " http://localhost:8080/site/restservices/a/b" and you need to use two matrix parameters, " p1" and " p2", in your JAX-RS service, you should use URLs ending like " http://localhost:8080/site/restservices/a/b;p1=1;p2=2". If you spread the matrix parameters into multiple path segments like " http://localhost:8080/site/restservices/a;p1=1/b;p2=2", then Apache CXF runtime will fail to match your JAX-RS service. In other words, Apache CXF will fail to find your JAX-RS service or operation with " /a/b" any more.

6. Generating API Documentation in Swagger Format

Since Bloomreach Experience Manager 12.1, it is possible to generate API documentation in Swagger format automatically for your custom plain JAX-RS service components.

To generate API documentation in Swagger format, add the following bean definition in a Spring bean assembly XML file located at site/src/main/resources/META-INF/hst-assembly/overrides/. e.g, swagger-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

  <bean id="swaggerBeanConfig" class="io.swagger.jaxrs.config.BeanConfig" init-method="setScan">
    <!--
      Assuming your custom plain JAX-RS service classes are in the 'com.example.rest.jaxrs.services' package.
      You can set multiple package names as a comma-separate string.
    -->
    <property name="resourcePackage" value="com.example.rest.jaxrs.services" />
    <!-- The title of your REST API -->
    <property name="title" value="My Example REST API" />
    <!-- The version of your REST API -->
    <property name="version" value="1.0" />
    <!-- The description of your REST API -->
    <property name="description" value="My Example REST Services" />
    <!-- Optional URL about the terms of the services of your REST API -->
    <property name="termsOfServiceUrl" value="http://www.example.com/terms-of-services.html" />
    <!-- Optional license information of your REST API -->
    <property name="license" value="Apache License, Version 2.0" />
    <!-- Optional license information URL of your REST API -->
    <property name="licenseUrl" value="https://www.apache.org/licenses/LICENSE-2.0" />
    <!-- Optional contact information of your REST API -->
    <property name="contact" value="[email protected]" />
  </bean>

</beans>

When you rebuild and restart the project, you will be able to read the auto-generated API documentation in Swagger format by the path (either /swagger.yaml or /swagger.json) after the REST API mount path (e.g, "/restservices"). That is, /restservices/swagger.yaml or /restservices/swagger.json, for instance.

If a Swagger UI web application is installed and configured to consume the auto-generated API documentation, developers can read the API specifications and try them out easily.

If you want to add and test a Swagger UI web application module (e.g, http://localhost:8080/api-docs/) locally in your project, see an example web application submodule ("api-docs") and its configurations (in root pom.xml and cargo.run profile) in the TestSuite project.

7. Summary

  • HST-2 supports normal standard based JAX-RS Components.

  • You can take advantage of HST Content Beans to retrieve/manage JCR contents in your Plain JAX-RS Components.

  • Unlike with Content/Context Aware JAX-RS Components, you will need to know the URL patterns on each resource service in order to access them.

  • Since v12.1, with a simple configuration, the API documentation (/swagger.yaml or /swagger.json) in Swagger format can be generated automatically. You can let developers use Swagger UI to understand the API specifications and try them out easily. 

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?