Creating REST endpoints in Hippo
Gary Law
2015-06-24
I occasionally spend time researching tech or frameworks and I have recently spent some time exploring hippo CMS for possible use in projects in the future. I would like to say that I am very impressed with this Java-based CMS and its flexibility. This article, however, is not to explain why I'm impressed but to explain how to expose a RESTful API for resources created in the CMS.
I will presume the following;
- You have a Hippo project up and running on localhost.
- You have some knowledge of Java Maven Projects.
- You know how to rebuild the project.
- You know the terminology of Hippo in regards to Documents.
Generating the Beans
When using the CMS UI to create document types the Java beans for these document types are not generated automatically and the beans are needed for the Rest API. This is one part that threw me as I did not know this had to be done for the Document Types to appear in the REST API Setup Tool.
This is where we use the Beanwriter tool in the essentials application. It is presumed you have come across the essentials application on the initial setup of your hippo CMS project but in case not, when the Hippo project is running it can be accessed via the url;
localhost:8080/essentials
On the left-hand side, you will see the menu in which there is tools item. In here you will find the Beanwriter. When you have the Beanwriter open you should see a list of your Document Types. Check each of the document types you wish to generate Beans for, so each one you want to create a REST Endpoint for and then click 'Generate HST Content Beans'.
At this point, we want to shut down the Hippo CMS and have a look at the source for the project.
We want to look at the Java source in the site project of the hippo source. In here we will find the generated Beans. The package will depend on what you set as the package path during the set up of the project. So if you package path is;
com.mydomain
The beans will be in;
<HippoSource>/site/src/main/java/com/mydomain/beans
You should see a class for each of the document types you selected in the Beanwriter.
The REST setup
In this next part, we will be using the Rest Service Setup tool that can be found in the essentials application. This is not installed by default so if you haven't you should install it now and rebuild the hippo CMS as instructed. This utility can be found in the library in the essentials application.
Although I know this tool does not complete everything to get your API up and running we are going to use it and I will show you what it modifies/creates in the source, and what it does not complete.
Ok so now we go to the Rest Services Setup tool.
In here the first thing we need to do is set the endpoint name in the field where we see ''. I personally use 'api'. So the url in completion would be;
http://localhost:8080/site/api
Below this, you will see where you can select the document types for which you can create RESTful Endpoints.
Note: This part was a gotcha for me as I did not realise you have to have beans generated for them to appear here in this list.
Select the Document types you wish to create endpoints for and then click 'Run setup'.
You will notice the tool is telling you that you need to rebuild for this to take effect so shut down the Hippo CMS.
We are now going to take a look at the source for the hippo project again to see what's been done, and also to see what's missing.
First, we are going to look at the hosts file inside the bootstrap project which can be found at;
<HippoSource>/bootstrap/src/main/resources/hst/hosts.xml
This file contains the mount points for the JAXRS (REST) Services and when we look inside this file we should see a declaration as follows;
...
<sv:node sv:name="api">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:mount</sv:value>
</sv:property>
<sv:property sv:name="hst:alias" sv:type="String">
<sv:value>api</sv:value>
</sv:property>
<sv:property sv:name="hst:ismapped" sv:type="Boolean">
<sv:value>false</sv:value>
</sv:property>
<sv:property sv:name="hst:namedpipeline" sv:type="String">
<sv:value>JaxrsRestPlainPipeline</sv:value>
</sv:property>
<sv:property sv:name="hst:types" sv:type="String" sv:multiple="true">
<sv:value>rest</sv:value>
</sv:property>
</sv:node>
...
You will notice
<sv:node sv:name="api">
and
<sv:property sv:name="hst:alias" sv:type="String">
<sv:value>api</sv:value>
</sv:property>
and that these declare the 'api' string that we entered as the value for the Endpoint name.
We don't need to do anything to this file I just wanted to show you where the mount point declaration was created.
Next, we going to go back to look at the 'site' project Java source and have a look at the Rest Endpoints created. For the sakes of this article, I suggest that the Document Type we have just created the RESTful Service for is one of 'Product'.
Again the package where the classes are created depends on the package path set on creation of the Hippo project and again we will presume com.mydomain as we did when we looked at the Beans creation. So in this case, if it doesn't exist the Rest Service Setup tool would have created the directory
<HippoSource>/site/src/main/java/com/mydomain/rest
and in here we should see the class
ProductResource.java
and in this class we will see
com.mydomain.rest
...
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
@Path("/product/")
public class ProductResource extends BaseRestResource {
@GET
@Path("/")
public Pageable<Product> index(@Context HttpServletRequest request) {
return findBeans(new DefaultRestContext(this, request), Product.class);
}
@GET
@Path("/page/{page}")
public Pageable<Product> page(@Context HttpServletRequest request, @PathParam("page") int page) {
return findBeans(new DefaultRestContext(this, request, page, DefaultRestContext.PAGE_SIZE), Product.class);
}
@GET
@Path("/page/{page}/{pageSize}")
public Pageable<Product> pageForSize(@Context HttpServletRequest request, @PathParam("page") int page, @PathParam("pageSize") int pageSize) {
return findBeans(new DefaultRestContext(this, request, page, pageSize), Product.class);
}
}
Although quite basic this class provides the endpoints for the full list or paginated lists for all the Documents in the CMS that are of the type Product.
Ok, that's great but know I will show you what the Rest Service Setup tool doesn't do that had me stumped for a while. For this we need to open the file;
<HippoSource>/site/src/main/resources/META-INF/hst-assembly/overrides/spring-plain-rest-api.xml
The CXF JaxRS implementation doesn't seem to automatically pick up the resources and so in this file we need to manually add them. In this file you will see;
<bean id="customRestPlainResourceProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
...
</list>
</property>
Inside the list element, we need to add a declaration for each of our Rest services. So in our case, we are going to add a declaration for the ProductResource.java that we have just looked at. We do this by adding an element to the list as follows;
<bean id="customRestPlainResourceProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
...
<bean class="org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider">
<constructor-arg>
<bean class="com.mydomain.rest.ProductResource"/>
</constructor-arg>
</bean>
...
</list>
</property>
If you have more services you need to add a declaration for each of the classes in the rest package. We are now ready to restart the Hippo CMS and test the endpoint.
If you do not have any documents in your CMS that are of one of the types we have created Beans and services for please do so.Again presuming one of these types is of type Product we can test using curl with the following command.
curl -H "Content-type:application/json" -H "Accept:application/json" http://localhost:8080/site/api/product
I hope this helps anyone get over the gotchas that stumped me.