RESTful JAX-RS Component Support

Info: This page assumes that Bloomreach Content is configured in multi-webapp mode. If your project uses single-webapp mode, adjust file paths and configuration settings as needed. See Single vs Multi Webapp Mode for details.

Overview

This page describes how to create custom JAX-RS-based RESTful web services in the delivery tier of Bloomreach Content.

Purpose

Enable custom JAX-RS RESTful web services within the delivery tier.

Service Types

Bloomreach Content supports two types of JAX-RS services:

  1. Plain JAX-RS services: Expose preconfigured functionality within a site. These services are not directly mapped to content but can access it.
  2. Context-aware JAX-RS services: Dynamically expose site content based on content type mappings.

Both service types are integrated into the HST and have full access to the HST API.

The Essentials setup application includes a REST Services Setup tool to assist with configuring plain JAX-RS services.

For implementation details, refer to this page and the dedicated guides for plain and context-aware services.

Enabling JAX-RS Services

If you created your project using the Bloomreach Content Maven archetype, JAX-RS support is already enabled. No further steps are required.

To enable JAX-RS support in other projects:

  1. Add the following dependency to the site/components module's pom.xml:

    <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-annotation_1.1_spec</artifactId> <version>1.0.1</version> <!-- Use 'provided' scope if your application container supplies javax.annotation.security --> <scope>compile</scope> </dependency>
  2. Add a Spring configuration file at /META-INF/hst-assembly/overrides/custom-jaxrs-resources.xml in the site/components module:

    <?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"> <!-- Import 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" /> <!-- List for custom Plain JAX-RS Resource Providers --> <bean id="customRestPlainResourceProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> </list> </property> </bean> <!-- List for custom Content/Context-Aware JAX-RS Resource Providers --> <bean id="customRestContentResourceProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> </list> </property> </bean> </beans>

    The three imports must be present to enable either the JaxrsRestPlainPipeline or JaxrsRestContentPipeline.

    • Add your custom Plain RESTful JAX-RS components to the sourceList of customRestPlainResourceProviders. These are used by the JaxrsRestPlainPipeline.
    • Add your custom Content/Context-Aware RESTful JAX-RS components to the sourceList of customRestContentResourceProviders. These are used by the JaxrsRestContentPipeline.

    Info: Spring bean configuration files in classpath:/META-INF/hst-assembly/overrides/*.xml are automatically loaded and merged unless you set the assembly.overrides property to an empty string in /WEB-INF/hst-config.properties.

XML and JSON Payload Handling

The delivery tier uses Apache CXF as the JAX-RS runtime. Apache CXF automatically consumes and produces XML or JSON payloads based on the request, even if the JAX-RS resource beans do not explicitly specify a format.

By default, Apache CXF inspects the "Accept" HTTP request header:

  • If the header is "text/xml" or "application/xml", XML is used.
  • If the header is "application/json", JSON is used.

Apache CXF also supports a special request parameter, _type. For example:

  • ?_type=xml or ...&_type=xml forces XML output.
  • ?_type=json or ...&_type=json forces JSON output.

Customizing the _type Parameter Name

You can change the default _type parameter name by adding configuration to your Spring assembly XML file:

<!-- Use '_format' as a parameter name instead of the default '_type' for the plain JAX-RS pipeline. Setting 'additionalQueryString' forces all JAX-RS requests to include additional parameters. For example, '_type=json' globally enforces JSON output regardless of the 'Accept' header. --> <bean id="jaxrsRestPlainServiceQueryStringReplacingInterceptor" class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor"> <property name="paramNameReplaces"> <map> <!-- Replace '_format' with '_type' before JAX-RS processing --> <entry key="_format" value="_type" /> </map> </property> <property name="additionalQueryString"> <value></value> <!-- To force JSON output globally: <value>_type=json</value> --> </property> </bean> <!-- Use '_format' as a parameter name instead of the default '_type' for the Content/Context-Aware JAX-RS pipeline. Setting 'additionalQueryString' forces all JAX-RS requests to include additional parameters. --> <bean id="jaxrsRestContentServiceQueryStringReplacingInterceptor" class="org.hippoecm.hst.jaxrs.cxf.QueryStringReplacingInterceptor"> <property name="paramNameReplaces"> <map> <entry key="_format" value="_type" /> </map> </property> <property name="additionalQueryString"> <value></value> <!-- To force JSON output globally: <value>_type=json</value> --> </property> </bean>
  • jaxrsRestPlainServiceQueryStringReplacingInterceptor applies to the Plain JAX-RS Service Pipeline.
  • jaxrsRestContentServiceQueryStringReplacingInterceptor applies to the Content/Context-Aware JAX-RS Service Pipeline.

Configure the paramNameReplaces property with parameter replacement pairs. In the example above, if a request includes _format, it is replaced with _type before processing. For example, ?_format=json is treated as ?_type=json.

Forcing Default Output Format

To enforce a default output format (JSON or XML) regardless of the "Accept" header, set the additionalQueryString property to _type=json or _type=xml.

By default, additionalQueryString is empty. If you set it to _type=json, the container appends this parameter to every request, overriding the client's "Accept" header. However, if the client explicitly specifies _type, that value takes precedence.

Configuring JAX-RS Extension Providers

JAX-RS supports the javax.ws.rs.ext.Provider interface for extensions such as MessageBodyReader, MessageBodyWriter, ContextResolver, and ExceptionMapper.

For example, you can map a custom Java exception to a specific HTTP error response using an ExceptionMapper:

package org.hippoecm.hst.demo.jaxrs.ext; @Provider public class MyCustomSecurityExceptionMapper implements ExceptionMapper<MyCustomSecurityException> { public Response toResponse(MyCustomSecurityException ex) { return Response.status(Response.Status.FORBIDDEN).build(); } }

Register your provider in a Spring assembly file, for example 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 is then registered with the JAX-RS runtime (Apache CXF).

Security Annotation Support

HST supports authentication and authorization per site mount or site map item. This allows you to control access at the site or sitemap level.

For finer-grained control, you can secure individual JAX-RS component operations using Java EE security annotations:

  • javax.annotation.security.DenyAll
  • javax.annotation.security.PermitAll
  • javax.annotation.security.RolesAllowed

Example usage:

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) { // Implementation return null; } @RolesAllowed(value={"author", "editor"}) @GET @Path("/body/") public HippoHtmlRepresentation getHippoHtmlRepresentation( @Context HttpServletRequest servletRequest, @Context HttpServletResponse servletResponse) { return super.getHippoHtmlRepresentation(servletRequest, null, null); } }

In this example:

  • getProductResource() is accessible to users with the "everybody" role.
  • getHippoHtmlRepresentation() is accessible only to users with the "author" or "editor" roles.

@Persistable Annotation Support

HST supports the @Persistable annotation (org.hippoecm.hst.content.annotations.Persistable) for JAX-RS service bean operations. This applies to both context/content-aware and plain JAX-RS service beans.

When you annotate a JAX-RS service operation with @Persistable, all calls to HstRequestContext#getSession() within that method return a JCR session from the writable session pool. This eliminates the need to manually manage separate read and write sessions.

Hint: Ensure the sitewriter user has write access before performing RESTful POST operations. See Access rights when you want to use workflow from the HST.

Example:

@Path("/products/") public class ProductPlainResource extends org.hippoecm.hst.jaxrs.services.AbstractResource { @GET @Path("/search/") public List<ProductRepresentation> searchProductResources( @Context HttpServletRequest servletRequest, @Context HttpServletResponse servletResponse, @Context UriInfo uriInfo) { List<ProductRepresentation> products = new ArrayList<ProductRepresentation>(); try { HstRequestContext requestContext = RequestContextProvider.get(); // Uses default session pool (not writable) since not annotated with @Persistable HstQueryManager hstQueryManager = getHstQueryManager(requestContext); 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 { // Uses writable session pool due to @Persistable annotation 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 this example, the createProductResources() method is annotated with @Persistable. All calls to HstRequestContext#getSession() within this method use a writable JCR session. Use @Persistable for operations that need to persist data to the repository.

The ProductBean class used here must implement the CodeBinderInterface to map bean properties to node properties.

@ParametersInfo Annotation Support

Starting with brXM 12.1, HST supports the @ParametersInfo annotation in JAX-RS service components for both plain and context/content-aware services.

To use this feature:

  • Annotate the JAX-RS service class with @ParametersInfo.
  • Add a @Context ParametersInfoProvider argument to each method that requires access to parameters.

Example:

@Path("/products/") @ParametersInfo(type=ProductPlainResourceInfo.class) public class ProductPlainResource extends org.hippoecm.hst.jaxrs.services.AbstractResource { @GET @Path("/search/") public List<ProductRepresentation> searchProductResources( @Context HttpServletRequest servletRequest, @Context HttpServletResponse servletResponse, @Context UriInfo uriInfo, @Context ParametersInfoProvider paramsInfoProvider) { // Retrieve the parameters info instance from the ParametersInfoProvider context argument final ProductPlainResourceInfo paramsInfo = paramsInfoProvider.getParametersInfo(); // Access parameters final String scopePath = paramsInfo.getScopePath(); final String tags = paramsInfo.getTags(); final String sortFields = paramsInfo.getSortFields(); final String sortOrder = paramsInfo.getSortOrder(); // Implement query logic based on parameters // ... } }

Retrieve the parameters info type instance using the @Context ParametersInfoProvider argument.

In plain JAX-RS service components, parameters are read from the resolved HST Mount.


For more details on each feature, refer to the linked documentation pages.

Share Feedback
Page: /build/rest-services/jax-rs-services/restful-jax-rs-component-support-in-hst-2
Section: Build
Category *