I will start a series of blog posts describing on how to use third party frameworks integrated with Liferay, based on my past experience as a core engineer, team leader, solution architect and consultant on large enterprise projects. I was often faced with challenges on how to integrate Liferay with frameworks such as Spring, while wanting to take advantage of our Liferay Service Builder for obvious reasons.
On this blog post I will describe step by step how to create RESTFul services using Liferay, Spring MVC, Liferay Service Builder and Maven, and how to leverage Spring framework, integrating that with our Permission System, and testing it all with SOAP UI. You can find the source code here
Some of you might ask, why RESTFul when we could use JSON WS out of the box? My standard answer is that you should use as many out of the box features as possible
But in large projects using standard Liferay MVC with Remote Services might not be efficient or sometimes in projects you are faced with customer requirements regarding their current architecture.
Or sometimes choosing RESTFul services might be simply an architecture decision because in fact it provides a clean, smart, friendly and optimized solution. RESTFul services are described using a WADL file so all of your frontend can be looked at as one big standard API, and in this case using JSON.
For the ones that have never worked with RESTFul services take a look at the wikipedia article
You can also know more about Spring RESTFul Web Services
The basic concept that I will illustrate is how to perform an http GET, PUT, POST, UPDATE, DELETE for CRUD operations, and how to return the appropriate Http Status Codes, by performing exception handling
An experienced developer will want to take advantage of both Liferay and Spring and sometimes you are not sure on how to do it at all. Most Liferay examples tend to cover our standard architecture but as you know, you can develop portlets in pretty much you can think of and nowadays you can even plug the Application Display Templates framework to rewrite your UI using Freemarker or Velocity.
So in my sample project I will produce RESTFul services that will be consumed by custom portlets and other third party systems. These RESTFul services will interact with custom Liferay Services to provide CRUD operations .
I have decided to use Spring MVC as the Web Framework for this project for several reasons:
- Liferay uses Spring internally (in a real project a team of Liferay developers would need to know Spring to understand how Liferay works)
-Spring has great support for Portlets
-Spring has great support for RESTFul Web Services
Before starting to build the solution we need to understand some things:
- Liferay is using Spring internally and for all of the custom services using Service Builder (one needs to be extra careful with Spring versions)
- Spring is a complex framework and in this project we will use Spring MVC for portlets (to produce a custom portlet) plus Spring MVC for servlets (to create a RESTFul API that can be consumed by the sample portlet and third party systems)
- Although we are using MVC, by using Service Builder we are using AOP, IOC, etc so a lot of additional Spring modules
- We need to keep in mind what Liferay provides out of the box and what Spring provides out of the box, and make decisions because there is some overlapping between features but what we want is to leverage Liferay and use Spring when needed, not override Liferay features with Spring ones (example Liferay Permission System vs Spring Security), we would be better off looking at both approaches and decide on how to to integrate both or choose just the Liferay bit.
Setting up the project:
1 - Maven
If you have already used or Liferay Developer Studio v 2.0 you can see how easy it is to create a Maven Project

You can read more about maven support from our guide
https://www.liferay.com/documentation/liferay-portal/6.2/development/-/ai/developing-plugins-using-maven-liferay-portal-6-2-dev-guide-02-en
There are also tons of blog posts you can read about maven in general and applied to Liferay
You can of course find the pom.xml together with the source code at my GitHub. Notice the Spring MVC dependencies and Jackson dependencies. Jackson is used as a framework that transforms objects into JSON format and vice-versa.

I have one active profile dev-local, but you can have several ones for different environments
This is extremely useful for deployments

If I want to deploy my maven project I simply execute the command:
mvn compile package liferay:deploy -P dev-local
I have told before that we are using Spring MVC but which version should you use?
As a rule of thumb I would use the same Spring Version that Liferay is using
For 6.2 unfortunately it is still 3.0.7 which is a bit old but it provides all the support we need for this project. Lets keep in mind we will be using Liferay Service Builder that is amazing, with all of the Spring files for transaction management, cache support, cluster awareness, indexing, inline permissions, soap and json APIs,etc is so positive that for me clearly it is worthy of having an older version in my project (its already version 3!) and having compatibility with Liferay generated source code. If you want to use a newer version you will face problems using our Service Builder also keep in mind possible classloader issues because we use Spring to do all of our heavy lifting and classloading magic.
2 - Spring MVC Configuration
We now need to setup Spring MVC for our Portlet and for our servlet that will serve RESTFul services
Portlet configuration is simple we just need to use the DispatcherPortlet and define a contextConfigLocation that points to a valid Spring files.
Notice that the Spring Beans and controllers are all in the Portlet Web Application Context

We still need to configure the Web xml to add servlet support for spring plus the servlet view that will allow the portlet to serve a jsp when the renderRequest is performed (we will se about that later)

The Spring beans injected by this PortalDelegateServlet are loaded by the global Web Application Context. So after portlet.xml and web.xml configuration we now need to create the spring files for both the Portlet and the Servlet.
We will use annotations for the controllers so the application context xml is really simple
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<context:component-scan base-package="com.liferay.samples.portlet"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<context:component-scan base-package="com.liferay.samples.servlet" />
<mvc:annotation-driven />
<mvc:interceptors>
<mvc:interceptor >
<mvc:mapping path="/helloSample"/>
<mvc:mapping path="/helloSample/sampleId/*"/>
<bean class="com.liferay.samples.servlet.interceptor.SecureRequestHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="html" value="text/html"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="prefixJson" value="true"/>
</bean>
</list>
</property>
</bean>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="com.liferay.samples.rest.json.LiferayObjectMapper" />
</beans>
Create a service.xml inside the WEB-INF folder
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd"> <service-builder package-path="com.liferay.samples.services"> <author>Liferay</author> <namespace>SAMPLES</namespace> <entity name="HelloSample" local-service="true" remote-service="true" > <column name="sampleId" type="long" primary="true" /> <column name="sampleName" type="String" /> <column name="sampleAddress" type="String" /> </entity> </service-builder>
You can build the service using the UI button or execute the maven liferay:build-service goal
You can read more about Service Builder at:
https://www.liferay.com/documentation/liferay-portal/6.2/development/-/ai/what-is-service-builder-liferay-portal-6-2-dev-guide-04-en
Using Service Builder has several advantages for you:
- Generates all the spring files for IOC and AOP
- All of your custom services will respect the same patterns and look exactly the same as Liferay's.
- By using the same patterns you guarantee that you will be able to use all of the Liferay features in your portlet (Asset framework, Indexing, Permissions, Workflow, etc)
- It is really complicated to develop the same cache management, cluster awareness, performant architecture that the service builder gives you for free. Hibernate is there so you still have all the features you are used to.
- Service Builder is going to evolve naturally as our platform evolves too, once you migrate to newer versions, new features will be available out of the box for your services (for example the Recicle Bin feature for 6.2)
- You will save time and money.
4 - Portlet Controller
Because our controller will only be invoked when a renderPhase is invoked it is incredibly simple, with just one method, returning a string. This string will be resolved into a view (jsp) using the configuration defined in the portlet application context.
package com.liferay.samples.portlet; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("VIEW") public class SampleSpringMVCRESTFulPortlet { @RequestMapping public String initRESTFulApp(){ return "defaultView"; } }
Define @Controller to tell spring to map this class as a controller
Define @RequestMapping("VIEW") to tell spring this is a Portlet controller in VIEW mode
Create a method and use the @RequestMapping to serve this method by default when the render phase is invoked.
5 - RESTFul Controller
This controller is serving all of the REST calls. There are a lot of things you need to consider when creating a RESTFul controller. The first thing is you need to know the annotations used in spring to map requests and also the annotations for http status and annotations for exception handling.
So these are the Http requests implemented in the controller
GET /helloSample -> returns all samples as json and http status 200 OK
GET /helloSample/sampleId/{sampleId} -> a id is passed as a parameter and returns one entity with http status 200 OK or if the entity does not exist returns the http status 404 NOT FOUND
POST /helloSample -> creates an entity (the entity is passed as json format ) and returns a 201 Created or a 400 Bad Request if the creation fails
PUT /helloSample/sampleId/{sampleId} ->id passed as parameter plus entity passed as json format and returns 204 No Content or 400 Bad Request if the update fails
DELETE /helloSample/sampleId/{sampleId} -> id passed as parameter returns a 204 No Content or 404 Not Found if the entity id does not exist OR 400 Bad Request if the sampleID is empty
Note that the RESTFul "Specification" does not force you to return always the same codes in all cases, sometimes you can use PUT or POST to create or update entities. Here is the Controller implementation:
(...)
@Controller
public class SampleRESTFullController {
@RequestMapping(value = "/helloSample", method=RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public @ResponseBody List<HelloSample> helloSample() throws SystemException {
try {
return HelloSampleServiceUtil.getAllHelloSamples();
} catch (SystemException e) {
throw e;
}
}
@RequestMapping(value = "/helloSample/sampleId/{sampleId}", method=RequestMethod.GET)
public @ResponseBody HelloSample helloSample(@PathVariable("sampleId") Long sampleId, HttpServletRequest request) throws NoSuchHelloSampleException, SystemException {
try {
return HelloSampleServiceUtil.getHelloSample(sampleId);
} catch (PortalException e) {
throw (NoSuchHelloSampleException) e;
} catch (SystemException e) {
throw e;
}
}
@RequestMapping(value = "/helloSample", method=RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody HelloSample createSample(@RequestBody HelloSampleImpl helloSample) throws NoSuchHelloSampleException, SystemException {
try {
HelloSample createdObject = HelloSampleServiceUtil.addHelloSample(helloSample);
return createdObject;
} catch (SystemException e) {
throw e;
}
}
@RequestMapping(value = "/helloSample/sampleId/{sampleId}",method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
void update(@PathVariable("sampleId") long sampleId,@RequestBody HelloSampleImpl helloSample) throws SystemException {
try {
helloSample.setSampleId(sampleId);
HelloSampleServiceUtil.updateHelloSample(helloSample);
} catch (SystemException e) {
throw e;
}
}
@RequestMapping(value = "/helloSample/sampleId/{sampleId}",method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
void delete(@PathVariable("sampleId") long sampleId) throws SystemException, NoSuchHelloSampleException {
try {
HelloSampleServiceUtil.deleteHelloSample(sampleId);
} catch (SystemException e) {
throw e;
} catch (PortalException e) {
throw (NoSuchHelloSampleException) e;
}
}
(...)
Notice in Spring code how simply you can return a list of objects just by adding the @ResponseBody
public @ResponseBody List<HelloSample> helloSample()
To obtain a variable passed as a query parameter you simply add a variable as a parameter and annotate it as a @PathVariable, Spring will do all the casting for you. (@PathVariable("sampleId") Long sampleId)
If you need to have the servlet request just add that to the method as a parameter otherwise don't.
Even more cool is the ability of Spring to create complex object based on a json variable, by simply adding
@RequestBody HelloSampleImpl helloSample as a method parameter.
6.1 - Using the generated Entity as Model object
We will be tempted to use the generated entity as model object returned by the controller
It does not work out of the box
Luckily I can tell you how to do it.
The first thing is that Spring cannot use the generated interface so we need to pass the Implementation as a parameter
The second thing is that the implementation is not serializable
The third thing is there are properties internally in the generated entities that we a) don't need to transform into JSON and b) cannot transform into JSON
6.1.1 - Serialization
So in order to use the entity implementation one will need to use the Jackson annotation @JsonIgnoreProperties and add that to the implementation to filter out all of the fields that are not necessary.
@JsonIgnoreProperties({ "expandoBridge", "expandoBridgeAttributes","cachedModel","escapedModel","modelAttributes","modelClass","modelClassName","new","primaryKey","primaryKeyObj" })
public class HelloSampleImpl extends HelloSampleBaseImpl implements Serializable{
(...)
}
package com.liferay.samples.rest.json;
import com.liferay.portal.model.BaseModel;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.expando.model.ExpandoBridge;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializerProvider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.type.JavaType;
/***************************************************************************
* <b>Description:</b> An ObjectMapper to add mixIn annotations
* This will make all the service builder entities valid for jackson
* deserialization
* <b>Created:</b>20 Feb 2014 17:48:09 @author Vitor Silva
**************************************************************************/
public class LiferayObjectMapper extends ObjectMapper {
public LiferayObjectMapper() {
super();
}
public LiferayObjectMapper(JsonFactory jf, SerializerProvider sp,
DeserializerProvider dp, SerializationConfig sconfig,
DeserializationConfig dconfig) {
super(jf, sp, dp, sconfig, dconfig);
}
public LiferayObjectMapper(JsonFactory jf, SerializerProvider sp,
DeserializerProvider dp) {
super(jf, sp, dp);
}
public LiferayObjectMapper(JsonFactory jf) {
super(jf);
}
@Override
public boolean canDeserialize(JavaType type) {
DeserializationConfig desConfig = copyDeserializationConfig();
addMixInAnnotations(desConfig, type);
return _deserializerProvider.hasValueDeserializerFor(desConfig, type);
}
/***************************************************************************
* <b>Description:</b> Adds mix in annotations to filter out
* entity internal fields like expando that prevent deserialization
*
* <b>Created:</b>20 Feb 2014 16:57:31 @author Vitor Silva
* @param desConfig
**************************************************************************/
protected void addMixInAnnotations(DeserializationConfig desConfig, JavaType type) {
desConfig.addMixInAnnotations(type.getClass(), IgnoreExpandoAttributesMixIn.class);
}
abstract class IgnoreExpandoAttributesMixIn
{
@JsonIgnore public abstract void setExpandoBridgeAttributes(ServiceContext serviceContext);
@JsonIgnore public abstract void setExpandoBridgeAttributes(BaseModel<?> baseModel);
@JsonIgnore public abstract void setExpandoBridgeAttributes(ExpandoBridge expandoBridge);
}
}
(...)
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="com.liferay.samples.rest.json.LiferayObjectMapper" />
(...)
By adding a @ResponseStatus(HttpStatus.OK) to a method we are basically stating that the method returns OK usually but if there is an exception we need to return a different Http Status and we can do that easily in Spring by creating exception handling methods that will catch the exceptions thrown out of original methods and return different Http Status codes
There is a generated exception by Service Builder called NoSuchHelloSampleException and we can add a spring ResponseStatus as annotation, meaning that if that exception is thrown Spring returns the Http code automatically as response of the REST call
(...)
/**
* @author Liferay
*/
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such HelloSample")
public class NoSuchHelloSampleException extends NoSuchModelException {
(...)
(...)
@ResponseStatus(HttpStatus.CONFLICT)
@ExceptionHandler(SystemException.class)
public void handleConflict(SystemException se) {
if(se.getCause() instanceof ORMException){
throw (ORMException) se.getCause();
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ORMException.class)
public void handleBadRequest(SystemException se) {
}
(...)
<mvc:interceptors>
<mvc:interceptor >
<mvc:mapping path="/helloSample"/>
<mvc:mapping path="/helloSample/sampleId/*"/>
<bean class="com.liferay.samples.servlet.interceptor.SecureRequestHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
(...)
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if(METHODS_TO_CHECK.contains(request.getMethod().toUpperCase())){
if (_log.isDebugEnabled()) {
if (_httpsRequired) {
_log.debug("https is required");
}
else {
_log.debug("https is not required");
}
}
if (_httpsRequired && !request.isSecure()) {
if (_log.isDebugEnabled()) {
String completeURL = HttpUtil.getCompleteURL(request);
_log.debug("Securing " + completeURL);
}
StringBundler redirectURL = new StringBundler(5);
redirectURL.append(Http.HTTPS_WITH_SLASH);
redirectURL.append(request.getServerName());
redirectURL.append(request.getServletPath());
String queryString = request.getQueryString();
if (Validator.isNotNull(queryString)) {
redirectURL.append(StringPool.QUESTION);
redirectURL.append(request.getQueryString());
}
if (_log.isDebugEnabled()) {
_log.debug("Redirect to " + redirectURL);
}
response.sendRedirect(redirectURL.toString());
return false;
}
else {
if (_log.isDebugEnabled()) {
String completeURL = HttpUtil.getCompleteURL(request);
_log.debug("Not securing " + completeURL);
}
User user = PortalUtil.getUser(request);
if ((user != null) && !user.isDefaultUser()) {
request = setCredentials(
request, request.getSession(), user.getUserId(), null);
return true;
}
else {
if (_digestAuthEnabled) {
return digestAuth(request, response)!=null?true:false;
}
else if (_basicAuthEnabled) {
return basicAuth(request, response)!=null?true:false;
}
return false;
}
}
}
return true;
}
(...)
This SOAPUI project is inside the project on github
8.1 - GET
Here is an example of a GET execution

and the Http Status returned:

plus the JSON list returned

8.2 - POST

And the HTTP 201 response:

8.3 - PUT

If I try to update a non existent entity I will get an Http NOT FOUND

8.4 - DELETE
The delete is roughly the same as the PUT
it returns 204 if successful or 404 if the sampleId does not exist or 400 if the entity is not well formed
8.5 - WADL generation
A nice feature of SOAPUI is the ability to generate a Web Application Description Language file that will serve as a contract that can be consumed by third parties. The interesting thing with REST is that you can just model a controller in a way that it will comply with REST specification, thus allowing you to generate a WADL of your controller layer
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
<doc xml:lang="en" title="RESTFul"/>
<resources base="http://localhost:8080">
<resource path="sample-springmvc-restfull-portlet/services/helloSample" id="HelloSample">
<doc xml:lang="en" title="HelloSample"/>
<param name="sampleId" default="12502" type="xs:string" required="false" style="template" xmlns:xs="http://www.w3.org/2001/XMLSchema"/>
<resource path="sampleId/{sampleId}" id="{sampleId}">
<doc xml:lang="en" title="{sampleId}"/>
<param name="sampleId" default="{sampleId}" type="xs:string" required="false" style="template" xmlns:xs="http://www.w3.org/2001/XMLSchema"/>
<method name="GET" id="GET_SAMPLE_BY_ID">
<doc xml:lang="en" title="GET_SAMPLE_BY_ID"/>
<request/>
<response status="200">
<representation mediaType="application/json;charset=UTF-8" element="ns:Response" xmlns:ns="http://localhost/sample-springmvc-restfull-portlet/services/helloSample/sampleId/12502"/>
</response>
</method>
<method name="PUT" id="UPDATE_SAMPLE_BY_ID">
<doc xml:lang="en" title="UPDATE_SAMPLE_BY_ID"/>
<request>
<representation mediaType="application/json"/>
</request>
<response status="204">
<representation mediaType="" element="data"/>
</response>
<response status="">
<representation mediaType="application/json"/>
</response>
</method>
<method name="DELETE" id="DELETE_SAMPLE_BY_ID">
<doc xml:lang="en" title="DELETE_SAMPLE_BY_ID"/>
<request/>
<response status="405">
<representation mediaType="text/html;charset=utf-8" element="html"/>
</response>
<response status="204">
<representation mediaType="" element="data"/>
</response>
</method>
</resource>
<method name="GET" id="GET_ALL_SAMPLES">
<doc xml:lang="en" title="GET_ALL_SAMPLES"/>
<request/>
<response status="200">
<representation mediaType="application/json;charset=UTF-8"/>
</response>
</method>
<method name="POST" id="CREATE_SAMPLE">
<doc xml:lang="en" title="CREATE_SAMPLE"/>
<request>
<representation mediaType="application/json"/>
</request>
<response status="405 415">
<representation mediaType="text/html;charset=utf-8"/>
</response>
<response status="">
<representation mediaType="application/json"/>
</response>
<response status="201">
<representation mediaType="application/json"/>
</response>
</method>
</resource>
</resources>
</application>
On the next blog post I will continue developing this application by integrating Permissions and an external Javascript Library that we have not seen much in examples: EXTJS
We will perform AJAX calls to RESTFul Web Services in order to produce the UI client side, combining a Thin Server Architecture with a Service Oriented Architecture

