RESTful JAX-RS Component Support - Enterprise Java Content management system - Hippo CMS

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

RESTful JAX-RS Component Support

1. Introduction

Since version 7.5 Hippo's delivery tier supports Content/Context Aware JAX-RS Services as well as Plain JAX-RS Services. So developers can easily develop not only normal plain JAX-RS components, but also JAX-RS components which are aware of their own mapped contents with full HST-2 URL mappings and link rewriting supports.

If you want to have RESTful URLs aligning with your sitemap configuration like normal SITE applications, then you will need to use Content/Context Aware JAX-RS Services.

If you need only plain RESTful URLs without automatic site mapping or if you need to publish your own predefined REST API services, then you can use Plain JAX-RS Services.

Find proper information about those in the following:

HST-2 added Java EE Security Annotations support so that developers can easily build secured JAX-RS operations with simple annotations.

Also, HST-2 added @Persistable Annotation support to help developers to easily implement persistable JAX-RS services.

Hippo's setup application provides a REST Services Setup tool which enables easy setup of plain JAX-RS services.

2. How to Enable JAX-RS Services Support

JAX-RS Services Support is optionally provided by hst-jaxrs-2.xx.xx.jar, so you should enable this by adding dependencies for that and adding a Spring Framework beans assembly configuration into classpath:/META-INF/hst-assembly/overrides/ folder.

The dependencies you should add in your site application's pom.xml is the following:

<dependency>
  <groupId>org.apache.geronimo.specs</groupId>
  <artifactId>geronimo-annotation_1.1_spec</artifactId>
  <version>1.0.1</version>
  <!-- NOTE: You should use 'provided' instead of 'compile'
       when your application container provides
       javax.annotation.security package. -->
  <scope>compile</scope>
</dependency>


<dependency>
  <groupId>org.onehippo.cms7.hst.components</groupId>
  <artifactId>hst-jaxrs</artifactId>
  <version>${hippo.hst.version}</version>
</dependency>

Note that if you used the Hippo Maven website archetype as described in the Hippo Trails to bootstrap your project the above configurations are already setup by default for your convenience.

Also, for example, you can add the following file as /META-INF/hst-assembly/overrides/custom-jaxrs-resources.xml as a classpath resource:

<?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-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>
      </list>
    </property>
  </bean>

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

</beans>

In the configuration above, the three imports should be added first to use either the JaxrsRestPlainPipeline or the JaxrsRestContentPipeline.

Your custom Plain RESTful JAX-RS components will be added into the " sourceList" of " customRestPlainResourceProviders", which is merged and used by the JaxrsRestPlainPipeline later.

And, your custom Content/Context Aware RESTful JAX-RS components will be added into the " sourceList" of " customRestContentResourceProviders", which is merged and used by the JaxrsRestContentPipeline later.

NOTE: Since HST-2.20.00, Spring Framework Beans assembly configuration under classpath:/META-INF/hst-assembly/overrides/.xml* resources will be automatically read and merged if you do not explicitly set the property, " assembly.overrides", to an empty string in /WEB-INF/hst-config.properties.

3. Using XML or JSON Payloads

HST-2 adopted Apache CXF as the JAX-RS runtime engine. Apache CXF JAX-RS can consume and produce XML or JSON formatted payloads automatically even though the JAX-RS Resource Beans do not specify the format. By default, Apache CXF JAX-RS will read "Accept" HTTP Request header to detect the most proper payload format first. For example, if "Accept" header value from the client is "text/xml" or "application/xml", then the runtime will decide to use XML format by default. If "Accept" header value is "application/json", then the runtime will decide to use JSON format.

In addition, Apache CXF JAX-RS supports a special request parameter "_type". For example, if the request contains a request parameter like " ?_type=xml" or " ...&_type=xml", then the runtime will decide to use XML format by default. If the request contains a request parameter like " ?_type=json" or " ...&_type=json", then the runtime will use JSON format.

3.1. How to customize the "_type" parameter name

Since 2.22.07, CMS 7.7.4, HST-2 provides customization support for the default Apache CXF JAX-RS request parameter, " _type".

In order to customize this default request parameter, you need to add the following Spring Beans configuration fragments into your overriding spring assembly XML file(s):

 <!--
    Enabling to use '_format' parameter name instead of the CXF default
    _type parameter name for the plain JAX-RS pipeline. Also, if you set
    'additionalQueryString' property, then all the JAX-RS requests will
    have the additional parameters forcefully. For example, if you set it
    to '_type=json', then you can force the output format to JSON format
    globally without considering 'Accept' HTTP request header.
  -->
  <bean id="jaxrsRestPlainServiceQueryStringReplacingInterceptor"
     class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor">
    <property name="paramNameReplaces">
      <map>
        <!-- The following will replace '_format' parameter name with
             '_type' parameter name before JAX-RS processing. -->
        <entry key="_format" value="_type" />
      </map>
    </property>
    <property name="additionalQueryString">
      <value></value>
      <!-- The following will append additional query string before JAX-RS
           processing
      <value>_type=json</value>
      -->
    </property>
  </bean>

  <!--
    Enabling to use '_format' parameter name instead of the CXF default
    _type parameter name for the Content/Context-Aware JAX-RS pipeline.
    Also, if you set 'additionalQueryString' property, then all the JAX-RS
    requests will have the additional parameters forcefully. For example,
    if you set it to '_type=json', then you can force the output format to
    JSON format globally without considering 'Accept' HTTP request header.
  -->
  <bean id="jaxrsRestContentServiceQueryStringReplacingInterceptor"
     class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor">
    <property name="paramNameReplaces">
      <map>
        <!-- The following will replace '_format' parameter name with
             '_type' parameter name before JAX-RS processing. -->
        <entry key="_format" value="_type" />
      </map>
    </property>
    <property name="additionalQueryString">
      <value></value>
      <!-- The following will append additional query string before JAX-RS
           processing
      <value>_type=json</value>
      -->
    </property>
  </bean>

In the example above, the bean, " jaxrsRestPlainServiceQueryStringReplacingInterceptor", is for the default Plain JAX-RS Service Pipeline, and the bean, " jaxrsRestContentServiceQueryStringReplacingInterceptor", is for the default Content/Context-Aware JAX-RS Service Pipeline.

In any of those interceptor beans, you can configure " paramNameReplaces" property by parameter name replacement pairs. So, in the example above, if a request parameter named '_format' is provided, the parameter name will be replaced by '_type', which is known to the Apache CXF JAX-RS runtime as a special system parameter name. For example, if your request contains " ?_format=json" or " ...&_format=json", then it will be equivalent to " ?_type=json" or " ...&_type=json" with the configuration example above.

In this way, you can use a different parameter name for the Apache CXF JAX-RS internal "_type" system parameter name.

3.2. How to force to use JSON or XML format by default

Since 2.22.07, CMS 7.7.4, you can force your application to use either JSON or XML format only by default.

If you want to use only one format either JSON or XML by default in your system without considering "Accept" HTTP Request header, then you can configure the " additionalQueryString" property value by " _type=json" or " _type=xml".

In the example Spring configuration fragment above (see 3.1), the " additionalQueryString" property value is an empty string by default. But if you set it to " _type=json" like the commented block for instance, then HST-2 Container will append the configured query parameter(s) string into the request message forcefully. So, even though the client prefers XML format by Accept header (e.g., "Accept: text/xml"), Apache CXF JAX-RS runtime will use JSON format only when you configure " _type=json" for " additionalQueryString" property by default.

However, if the client code requests JAX-RS URL by explicitly specifying the format parameter (e.g., " _type=json" or " _type=xml"), then this default configuration will not be applied. In that case, the client-specified request parameter will be applied.

4. How to configure JAX-RS Extension Providers

The JAX-RS Specification supports the javax.ws.rs.ext.Provider interface which is a marker interface for an implementation of an extension such as javax.ws.rs.ext.MessageBodyReader, javax.ws.rs.ext.MessageBodyWriter, javax.ws.rs.ext.ContextResolver, and javax.ws.rs.ext.ExceptionMapper.

For example, you can add an extension point to return a specific HTTP error on a Java exception by configuring an ExceptionMapper. A simple ExceptionMapper implementation could be like this:

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

/**
 * This extension provider can be configured to be invoked whenever
 * MyCustomSecurityException occurs. In that case, this provider simply
 * returns a forbidden access status code. This provider can make the
 * remaining JAX-RS Resource operation implementations much more simplified.
 * Refer to the JAX-RS Specification for more detail.
 */
@Provider
public class MyCustomSecurityExceptionMapper
                  implements ExceptionMapper<MyCustomSecurityException> {
    public Response toResponse(MyCustomSecurityException ex) {
        return Response.status(Response.Status.FORBIDDEN).build();
    }
}

You can configure your providers in an overriding Spring assembly file. For example, you may add the following block in classpath:/META-INF/hst-assembly/overrides/custom-jaxrs-resources.xml:

<bean id="customJaxrsRestEntityProviders"
      class="org.springframework.beans.factory.config.ListFactoryBean">
  <property name="sourceList">
    <list>
      <bean class=
      "org.hippoecm.hst.demo.jaxrs.ext.MyCustomSecurityExceptionMapper" />
    </list>
  </property>
</bean>

Your provider(s) will then be registered with the JAX-RS runtime engine (Apache CXF) to provide and use the extension(s).

5. Security Annotations Support

HST-2 provides security authentication/authorization configurations per site mount or site map item as explained in HST-2 Authentication and Authorization Support. With that configuration you can manage secured access control per site mount or site map item.

However, sometimes you might need to do secured access control per JAX-RS component operation invocation. For example, you can allow every read access from an operation, but you can allow some updating operations only to some specified roles.

To fulfill this requirement, HST-2 supports the Java EE Security Annotations.

So, you can annotate your methods with the following Java EE Security Annotations:

  • javax.annotation.security.DenyAll

  • javax.annotation.security.PermitAll

  • javax.annotation.security.RolesAllowed

For example, you can add Java EE Security Annotations to your JAX-RS component code like this:

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

@Path("/demosite:productdocument/")
public class ProductContentResource extends AbstractContentResource {

    @RolesAllowed(value={"everybody"})
    @GET
    @Path("/")
    public ProductRepresentation getProductResource(
                      @Context HttpServletRequest servletRequest,
                      @Context HttpServletResponse servletResponse) {
        // do nothing for now...
        return null;
    }

    @RolesAllowed(value={"author", "editor"})
    @GET
    @Path("/body/")
    public HippoHtmlRepresentation getHippoHtmlRepresentation(
                      @Context HttpServletRequest servletRequest,
                      @Context HttpServletResponse servletResponse) {
        return super.getHippoHtmlRepresentation(servletRequest, null, null);
    }

}

With the example above, the #getProductResource() operation will be allowed for every user having the "everybody" role, while the #getHippoHtmlRepresentation() operation will be allowed only to users having the "author" or "editor" role.

6. @Persistable Annotation Support

As of HST-2.24.02, CMS 7.8.1, HST-2 supports @Persistable ( org.hippoecm.hst.content.annotations.Persistable) annotation in the JAX-RS Service bean operations (whether it is a Context/Content-Aware JAX-RS Service or Plain JAX-RS Service bean).

If you annotate your JAX-RS Service operations by @Persistable ( org.hippoecm.hst.content.annotations.Persistable) annotation, the following invocation will always return a JCR session from the writable session pool:

  • HstRequestContext#getSession()

So, you don't have to try to use different JCR sessions in an operation context. (e.g., reading with a JCR session from the default session pool and persisting with another JCR session from the writable session)

Instead, you can use only one JCR session from the writable session pool when you read beans and/or persist your beans to the repository. Also, this will reduce possible error-prone codes because you don't have to refresh a JCR session to read some contents again after persisting contents.

 

Make sure the sitewriter user has write access before doing a RESTful POST. This is described in page Access rights when you want to use workflow from the HST

Here is an example code, which can be found in the hippo-testsuite project (See the References below).

@Path("/products/")public class ProductPlainResource
                                       extends AbstractResource {

    @GET
    @Path("/search/")
    public List<ProductRepresentation> searchProductResources(
                           @Context HttpServletRequest servletRequest,
                           @Context HttpServletResponse servletResponse,
                           @Context UriInfo uriInfo) {

        List<ProductRepresentation> products =
                           new ArrayList<ProductRepresentation>();
        try {
           // You can use either getRequestContext(servletRequest) or
           // RequestContextProvider.get().
           // HstRequestContext requestContext =
           // getRequestContext(servletRequest);

           HstRequestContext requestContext = RequestContextProvider.get();

           // NOTE : AbstractResource#getHstQueryManager(requestContext)
           // simply get the JCR session by calling
           // requestContext.getSession().
           // If this operation is annotated by @Persistable, HST-2 JAX-RS
           // runtime engine returns a JCR session from the writable
           // session pool. However, this method is not annotated by
           // @Persistable, so it returns a JCR session from the default
           // session pool.

           HstQueryManager hstQueryManager =
                          getHstQueryManager(requestContext);

           // NOTE: AbstractResource#getMountContentBaseBean(requestContext)
           // simply get the JCR session by calling
           // requestContext.getSession().
           // If this operation is annotated by @Persistable, HST-2
           // JAX-RS runtime engine returns a JCR session from the
           // writable session pool However, this method is not
           // annotated by @Persistable, so it returns a JCR session from
           // the default session pool. for plain jaxrs, we do not have a
           // requestContentBean because no resolved sitemapitem

           HippoBean scope = getMountContentBaseBean(requestContext);

           HstQuery hstQuery = hstQueryManager.createQuery(scope,
                                             ProductBean.class, true);
           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);
                   products.add(productRep);
                }
            }

        } catch (Exception e) {
            throw new WebApplicationException(e,
                           ResponseUtils.buildServerErrorResponse(e));
        }

        return products;
    }

    @Persistable
    @POST
    public ProductRepresentation createProductResources(
                             @Context HttpServletRequest servletRequest,
                             @Context HttpServletResponse servletResponse,
                             @Context UriInfo uriInfo,
                             ProductRepresentation productRepresentation) {

        HstRequestContext requestContext =
                            getRequestContext(servletRequest);

        try {
            // NOTE:
            // AbstractResource#getPersistenceManager(requestContext)
            // simply get a JCR session by calling
            // requestContext.getSession().
            // This method is annotated by @Persistable, so HST-2 JAX-RS
            // runtime engine returns a JCR session from the writable
            // session pool.

            WorkflowPersistenceManager wpm = (WorkflowPersistenceManager)
                          getPersistenceManager(requestContext);
            HippoFolderBean contentBaseFolder =
                          getMountContentBaseBean(requestContext);
            String productFolderPath = contentBaseFolder.getPath() +
                          "/products";
            String beanPath = wpm.createAndReturn(productFolderPath,
                          "demosite:productdocument",
                           productRepresentation.getBrand(), true);
            ProductBean productBean = (ProductBean) wpm.getObject(beanPath);

            productBean.setBrand(productRepresentation.getBrand());
            productBean.setColor(productRepresentation.getColor());
            productBean.setType(productRepresentation.getType());
            productBean.setPrice(productRepresentation.getPrice());

            wpm.update(productBean);
            wpm.save();

            productBean = (ProductBean) wpm.getObject(productBean.getPath());
        } catch (ObjectBeanManagerException e) {
            throw new WebApplicationException(e,
                          ResponseUtils.buildServerErrorResponse(e));
        } catch (RepositoryException e) {
            throw new WebApplicationException(e,
                          ResponseUtils.buildServerErrorResponse(e));
        }

        return productRepresentation;
    }
}

In the example above, there is no code line to get a writable (persistable) JCR session manually. Instead, the #createProductResources() method is annotated by @Persistable. So, when HstRequestContext#getSession() is invoked, a JCR session will be retrieved from the writable session pool automatically. Every call on HstRequestContext#getSession() in the invoking context of the @Persistable annotated methods, will return a JCR session from the writable session pool automatically.

If your JAX-RS operation needs to persist beans into the repository, it is strongly recommended to use @Persistable annotation rather than to try to get a writable (persistable) JCR session manually.

References

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

RESTful JAX-RS Component Support

1. Introduction

Since version 7.5 Hippo's delivery tier supports Content/Context Aware JAX-RS Services as well as Plain JAX-RS Services. So developers can easily develop not only normal plain JAX-RS components, but also JAX-RS components which are aware of their own mapped contents with full HST-2 URL mappings and link rewriting supports.

If you want to have RESTful URLs aligning with your sitemap configuration like normal SITE applications, then you will need to use Content/Context Aware JAX-RS Services.

If you need only plain RESTful URLs without automatic site mapping or if you need to publish your own predefined REST API services, then you can use Plain JAX-RS Services.

Find proper information about those in the following:

HST-2 added Java EE Security Annotations support so that developers can easily build secured JAX-RS operations with simple annotations.

Also, HST-2 added @Persistable Annotation support to help developers to easily implement persistable JAX-RS services.

Hippo's setup application provides a REST Services Setup tool which enables easy setup of plain JAX-RS services.

2. How to Enable JAX-RS Services Support

JAX-RS Services Support is optionally provided by hst-jaxrs-2.xx.xx.jar, so you should enable this by adding dependencies for that and adding a Spring Framework beans assembly configuration into classpath:/META-INF/hst-assembly/overrides/ folder.

The dependencies you should add in your site application's pom.xml is the following:

<dependency>
  <groupId>org.apache.geronimo.specs</groupId>
  <artifactId>geronimo-annotation_1.1_spec</artifactId>
  <version>1.0.1</version>
  <!-- NOTE: You should use 'provided' instead of 'compile'
       when your application container provides
       javax.annotation.security package. -->
  <scope>compile</scope>
</dependency>


<dependency>
  <groupId>org.onehippo.cms7.hst.components</groupId>
  <artifactId>hst-jaxrs</artifactId>
  <version>${hippo.hst.version}</version>
</dependency>

Note that if you used the Hippo Maven website archetype as described in the Hippo Trails to bootstrap your project the above configurations are already setup by default for your convenience.

Also, for example, you can add the following file as /META-INF/hst-assembly/overrides/custom-jaxrs-resources.xml as a classpath resource:

<?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-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>
      </list>
    </property>
  </bean>

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

</beans>

In the configuration above, the three imports should be added first to use either the JaxrsRestPlainPipeline or the JaxrsRestContentPipeline.

Your custom Plain RESTful JAX-RS components will be added into the " sourceList" of " customRestPlainResourceProviders", which is merged and used by the JaxrsRestPlainPipeline later.

And, your custom Content/Context Aware RESTful JAX-RS components will be added into the " sourceList" of " customRestContentResourceProviders", which is merged and used by the JaxrsRestContentPipeline later.

NOTE: Since HST-2.20.00, Spring Framework Beans assembly configuration under classpath:/META-INF/hst-assembly/overrides/.xml* resources will be automatically read and merged if you do not explicitly set the property, " assembly.overrides", to an empty string in /WEB-INF/hst-config.properties.

3. Using XML or JSON Payloads

HST-2 adopted Apache CXF as the JAX-RS runtime engine. Apache CXF JAX-RS can consume and produce XML or JSON formatted payloads automatically even though the JAX-RS Resource Beans do not specify the format. By default, Apache CXF JAX-RS will read "Accept" HTTP Request header to detect the most proper payload format first. For example, if "Accept" header value from the client is "text/xml" or "application/xml", then the runtime will decide to use XML format by default. If "Accept" header value is "application/json", then the runtime will decide to use JSON format.

In addition, Apache CXF JAX-RS supports a special request parameter "_type". For example, if the request contains a request parameter like " ?_type=xml" or " ...&_type=xml", then the runtime will decide to use XML format by default. If the request contains a request parameter like " ?_type=json" or " ...&_type=json", then the runtime will use JSON format.

3.1. How to customize the "_type" parameter name

Since 2.22.07, CMS 7.7.4, HST-2 provides customization support for the default Apache CXF JAX-RS request parameter, " _type".

In order to customize this default request parameter, you need to add the following Spring Beans configuration fragments into your overriding spring assembly XML file(s):

 <!--
    Enabling to use '_format' parameter name instead of the CXF default
    _type parameter name for the plain JAX-RS pipeline. Also, if you set
    'additionalQueryString' property, then all the JAX-RS requests will
    have the additional parameters forcefully. For example, if you set it
    to '_type=json', then you can force the output format to JSON format
    globally without considering 'Accept' HTTP request header.
  -->
  <bean id="jaxrsRestPlainServiceQueryStringReplacingInterceptor"
     class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor">
    <property name="paramNameReplaces">
      <map>
        <!-- The following will replace '_format' parameter name with
             '_type' parameter name before JAX-RS processing. -->
        <entry key="_format" value="_type" />
      </map>
    </property>
    <property name="additionalQueryString">
      <value></value>
      <!-- The following will append additional query string before JAX-RS
           processing
      <value>_type=json</value>
      -->
    </property>
  </bean>

  <!--
    Enabling to use '_format' parameter name instead of the CXF default
    _type parameter name for the Content/Context-Aware JAX-RS pipeline.
    Also, if you set 'additionalQueryString' property, then all the JAX-RS
    requests will have the additional parameters forcefully. For example,
    if you set it to '_type=json', then you can force the output format to
    JSON format globally without considering 'Accept' HTTP request header.
  -->
  <bean id="jaxrsRestContentServiceQueryStringReplacingInterceptor"
     class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor">
    <property name="paramNameReplaces">
      <map>
        <!-- The following will replace '_format' parameter name with
             '_type' parameter name before JAX-RS processing. -->
        <entry key="_format" value="_type" />
      </map>
    </property>
    <property name="additionalQueryString">
      <value></value>
      <!-- The following will append additional query string before JAX-RS
           processing
      <value>_type=json</value>
      -->
    </property>
  </bean>

In the example above, the bean, " jaxrsRestPlainServiceQueryStringReplacingInterceptor", is for the default Plain JAX-RS Service Pipeline, and the bean, " jaxrsRestContentServiceQueryStringReplacingInterceptor", is for the default Content/Context-Aware JAX-RS Service Pipeline.

In any of those interceptor beans, you can configure " paramNameReplaces" property by parameter name replacement pairs. So, in the example above, if a request parameter named '_format' is provided, the parameter name will be replaced by '_type', which is known to the Apache CXF JAX-RS runtime as a special system parameter name. For example, if your request contains " ?_format=json" or " ...&_format=json", then it will be equivalent to " ?_type=json" or " ...&_type=json" with the configuration example above.

In this way, you can use a different parameter name for the Apache CXF JAX-RS internal "_type" system parameter name.

3.2. How to force to use JSON or XML format by default

Since 2.22.07, CMS 7.7.4, you can force your application to use either JSON or XML format only by default.

If you want to use only one format either JSON or XML by default in your system without considering "Accept" HTTP Request header, then you can configure the " additionalQueryString" property value by " _type=json" or " _type=xml".

In the example Spring configuration fragment above (see 3.1), the " additionalQueryString" property value is an empty string by default. But if you set it to " _type=json" like the commented block for instance, then HST-2 Container will append the configured query parameter(s) string into the request message forcefully. So, even though the client prefers XML format by Accept header (e.g., "Accept: text/xml"), Apache CXF JAX-RS runtime will use JSON format only when you configure " _type=json" for " additionalQueryString" property by default.

However, if the client code requests JAX-RS URL by explicitly specifying the format parameter (e.g., " _type=json" or " _type=xml"), then this default configuration will not be applied. In that case, the client-specified request parameter will be applied.

4. How to configure JAX-RS Extension Providers

The JAX-RS Specification supports the javax.ws.rs.ext.Provider interface which is a marker interface for an implementation of an extension such as javax.ws.rs.ext.MessageBodyReader, javax.ws.rs.ext.MessageBodyWriter, javax.ws.rs.ext.ContextResolver, and javax.ws.rs.ext.ExceptionMapper.

For example, you can add an extension point to return a specific HTTP error on a Java exception by configuring an ExceptionMapper. A simple ExceptionMapper implementation could be like this:

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

/**
 * This extension provider can be configured to be invoked whenever
 * MyCustomSecurityException occurs. In that case, this provider simply
 * returns a forbidden access status code. This provider can make the
 * remaining JAX-RS Resource operation implementations much more simplified.
 * Refer to the JAX-RS Specification for more detail.
 */
@Provider
public class MyCustomSecurityExceptionMapper
                  implements ExceptionMapper<MyCustomSecurityException> {
    public Response toResponse(MyCustomSecurityException ex) {
        return Response.status(Response.Status.FORBIDDEN).build();
    }
}

You can configure your providers in an overriding Spring assembly file. For example, you may add the following block in classpath:/META-INF/hst-assembly/overrides/custom-jaxrs-resources.xml:

<bean id="customJaxrsRestEntityProviders"
      class="org.springframework.beans.factory.config.ListFactoryBean">
  <property name="sourceList">
    <list>
      <bean class=
      "org.hippoecm.hst.demo.jaxrs.ext.MyCustomSecurityExceptionMapper" />
    </list>
  </property>
</bean>

Your provider(s) will then be registered with the JAX-RS runtime engine (Apache CXF) to provide and use the extension(s).

5. Security Annotations Support

HST-2 provides security authentication/authorization configurations per site mount or site map item as explained in HST-2 Authentication and Authorization Support. With that configuration you can manage secured access control per site mount or site map item.

However, sometimes you might need to do secured access control per JAX-RS component operation invocation. For example, you can allow every read access from an operation, but you can allow some updating operations only to some specified roles.

To fulfill this requirement, HST-2 supports the Java EE Security Annotations.

So, you can annotate your methods with the following Java EE Security Annotations:

  • javax.annotation.security.DenyAll

  • javax.annotation.security.PermitAll

  • javax.annotation.security.RolesAllowed

For example, you can add Java EE Security Annotations to your JAX-RS component code like this:

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

@Path("/demosite:productdocument/")
public class ProductContentResource extends AbstractContentResource {

    @RolesAllowed(value={"everybody"})
    @GET
    @Path("/")
    public ProductRepresentation getProductResource(
                      @Context HttpServletRequest servletRequest,
                      @Context HttpServletResponse servletResponse) {
        // do nothing for now...
        return null;
    }

    @RolesAllowed(value={"author", "editor"})
    @GET
    @Path("/body/")
    public HippoHtmlRepresentation getHippoHtmlRepresentation(
                      @Context HttpServletRequest servletRequest,
                      @Context HttpServletResponse servletResponse) {
        return super.getHippoHtmlRepresentation(servletRequest, null, null);
    }

}

With the example above, the #getProductResource() operation will be allowed for every user having the "everybody" role, while the #getHippoHtmlRepresentation() operation will be allowed only to users having the "author" or "editor" role.

6. @Persistable Annotation Support

As of HST-2.24.02, CMS 7.8.1, HST-2 supports @Persistable ( org.hippoecm.hst.content.annotations.Persistable) annotation in the JAX-RS Service bean operations (whether it is a Context/Content-Aware JAX-RS Service or Plain JAX-RS Service bean).

If you annotate your JAX-RS Service operations by @Persistable ( org.hippoecm.hst.content.annotations.Persistable) annotation, the following invocation will always return a JCR session from the writable session pool:

  • HstRequestContext#getSession()

So, you don't have to try to use different JCR sessions in an operation context. (e.g., reading with a JCR session from the default session pool and persisting with another JCR session from the writable session)

Instead, you can use only one JCR session from the writable session pool when you read beans and/or persist your beans to the repository. Also, this will reduce possible error-prone codes because you don't have to refresh a JCR session to read some contents again after persisting contents.

 

Make sure the sitewriter user has write access before doing a RESTful POST. This is described in page Access rights when you want to use workflow from the HST

Here is an example code, which can be found in the hippo-testsuite project (See the References below).

@Path("/products/")public class ProductPlainResource
                                       extends AbstractResource {

    @GET
    @Path("/search/")
    public List<ProductRepresentation> searchProductResources(
                           @Context HttpServletRequest servletRequest,
                           @Context HttpServletResponse servletResponse,
                           @Context UriInfo uriInfo) {

        List<ProductRepresentation> products =
                           new ArrayList<ProductRepresentation>();
        try {
           // You can use either getRequestContext(servletRequest) or
           // RequestContextProvider.get().
           // HstRequestContext requestContext =
           // getRequestContext(servletRequest);

           HstRequestContext requestContext = RequestContextProvider.get();

           // NOTE : AbstractResource#getHstQueryManager(requestContext)
           // simply get the JCR session by calling
           // requestContext.getSession().
           // If this operation is annotated by @Persistable, HST-2 JAX-RS
           // runtime engine returns a JCR session from the writable
           // session pool. However, this method is not annotated by
           // @Persistable, so it returns a JCR session from the default
           // session pool.

           HstQueryManager hstQueryManager =
                          getHstQueryManager(requestContext);

           // NOTE: AbstractResource#getMountContentBaseBean(requestContext)
           // simply get the JCR session by calling
           // requestContext.getSession().
           // If this operation is annotated by @Persistable, HST-2
           // JAX-RS runtime engine returns a JCR session from the
           // writable session pool However, this method is not
           // annotated by @Persistable, so it returns a JCR session from
           // the default session pool. for plain jaxrs, we do not have a
           // requestContentBean because no resolved sitemapitem

           HippoBean scope = getMountContentBaseBean(requestContext);

           HstQuery hstQuery = hstQueryManager.createQuery(scope,
                                             ProductBean.class, true);
           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);
                   products.add(productRep);
                }
            }

        } catch (Exception e) {
            throw new WebApplicationException(e,
                           ResponseUtils.buildServerErrorResponse(e));
        }

        return products;
    }

    @Persistable
    @POST
    public ProductRepresentation createProductResources(
                             @Context HttpServletRequest servletRequest,
                             @Context HttpServletResponse servletResponse,
                             @Context UriInfo uriInfo,
                             ProductRepresentation productRepresentation) {

        HstRequestContext requestContext =
                            getRequestContext(servletRequest);

        try {
            // NOTE:
            // AbstractResource#getPersistenceManager(requestContext)
            // simply get a JCR session by calling
            // requestContext.getSession().
            // This method is annotated by @Persistable, so HST-2 JAX-RS
            // runtime engine returns a JCR session from the writable
            // session pool.

            WorkflowPersistenceManager wpm = (WorkflowPersistenceManager)
                          getPersistenceManager(requestContext);
            HippoFolderBean contentBaseFolder =
                          getMountContentBaseBean(requestContext);
            String productFolderPath = contentBaseFolder.getPath() +
                          "/products";
            String beanPath = wpm.createAndReturn(productFolderPath,
                          "demosite:productdocument",
                           productRepresentation.getBrand(), true);
            ProductBean productBean = (ProductBean) wpm.getObject(beanPath);

            productBean.setBrand(productRepresentation.getBrand());
            productBean.setColor(productRepresentation.getColor());
            productBean.setType(productRepresentation.getType());
            productBean.setPrice(productRepresentation.getPrice());

            wpm.update(productBean);
            wpm.save();

            productBean = (ProductBean) wpm.getObject(productBean.getPath());
        } catch (ObjectBeanManagerException e) {
            throw new WebApplicationException(e,
                          ResponseUtils.buildServerErrorResponse(e));
        } catch (RepositoryException e) {
            throw new WebApplicationException(e,
                          ResponseUtils.buildServerErrorResponse(e));
        }

        return productRepresentation;
    }
}

In the example above, there is no code line to get a writable (persistable) JCR session manually. Instead, the #createProductResources() method is annotated by @Persistable. So, when HstRequestContext#getSession() is invoked, a JCR session will be retrieved from the writable session pool automatically. Every call on HstRequestContext#getSession() in the invoking context of the @Persistable annotated methods, will return a JCR session from the writable session pool automatically.

If your JAX-RS operation needs to persist beans into the repository, it is strongly recommended to use @Persistable annotation rather than to try to get a writable (persistable) JCR session manually.

References