Using RESTFul services with Liferay

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>
 
The application context for the RESTFull servlet is a bit more complicated because of some details regarding content negotiation spring interceptors and custom jackson object mappers we need but it I am going to describe this beans when they are necessary.
<?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>
 
3 - Using Liferay Service Builder to generate out CRUD operations

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>
We are going to generate Local services and Remote services, the remote services also provide Remote API's via SOAP and JSON

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{

(...)

}
6.1.2 - Deserialization
When there is a request sent to the server the JSON object is going to be transformed into the HelloSampleImpl using a number of different techniques by Spring. The Deserialization process will
We need to filter the fields we do not with to deserialize, and tell Jackson to ignore that altogether.
This is done by creating our own Jackson ObjectMapper where we will inject MixIn annotations in order to properly reimplement the canDeserialize method. 
 
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);

}



}
The MixIn annotations are added generically to all entities so it will work for all service builder entities from now on.
 
After that one needs to add the custom Object Mapper to Spring's Web Application Context configuration
 
(...)
 <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" />

    (...)
 
6.2 - Exception Handling

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 {

(...)
We can manipulate exceptions thrown from the service layer in the Controller class by adding annotated methods for exception handling
 
(...)
@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) {  

}

(...)
 
7 - Integrating Permission System
In order to integrate Liferay Permission System (for external access) we need to provide the ability to authenticate a user and initialize the permission system. This is a bit tricky we need to understand a couple of principles. How can we authenticate a user in Liferay in a web service call? What is the best way to do it?  Should we use Spring security?
I based myself on SecureFilter implementation, that is used (or was in 6.1) to authenticate remote servlet requests. It knows how to initialize the necessary thread locals and what to add to the http session etc
So I will use most of the SecureFilter's code. Now it is just a matter to decide where to add it
To be fair I cannot say where is definitely the best place to use it (My gut feeling is Spring Security AccessDecisionVoter but it is not clear). I have decided not to integrate Spring Security. I have decided to create a HandlerInterceptor to intercept the REST requests and validate the users. In Spring framework you can add interceptors to pretty much anywhere, specially servlet requests
I then mapped some URLs to that interceptor using Spring
 
<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>
And the handler interceptor code (not fully tested):
(...)
@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;
}
(...)
 
 
8 - Testing the RESTFul Services with SOAPUI
It is extremely easy to test the developed RESTFul services using SOAPUI. SOAPUI has great support to generate the web services calls, test cases and WADL file (similar to WSDL but for REST services)
I started by creating a REST call to retrieve all the samples
After starting SOAP UI you just need to do File->New->REST Project and add that http get request
 
After that I just added the other http requests like the PUT, POST and DELETE
Notice that for de DELETE, PUT and GET of a single entity you need to pass an entity ID so I added those services as children

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

 

 

Blogs
Vitor Silva,
Excellent post!!! I happy to see this post as it gave clear a path for all developers who are struggling to include Ext JS with Lifeay.
Thanks for the article. It does not work in the Jboss server. I get following error :

Caused by: org.springframework.beans.FatalBeanException: Class [org.springframework.context.config.ContextNamespaceHandler] for namespace [http://www.springframework.org/schema/context] does not implement the [org.springframework.beans.factory.xml.NamespaceHandler] interface
at org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve(DefaultNamespaceHandlerResolver.java:126) [spring-beans.jar:3.0.7.RELEASE]
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1333) [spring-beans.jar:3.0.7.RELEASE]
Nice post...emoticon
But can you please provide client program in java to handle the return values like getAllSamples()....
Thank you a TON for these examples. Seems that this is exactly what we are looking for. However, While extracting some of these examples... we are seeing a HTTP Status 405 from the Apache Tomcat server. Is this common?
Sorry but @JsonIgnoreProperties doesn't have effect to me.
I'm using 6.2.4, look at this please:
https://gist.github.com/baxtheman/457f5dfd214d2ef1a36a
Hi Vitor,
while trying to add this RESTFul Api to an existing portlet-project I was fighting with an htpp 500 Response when returning List of objects and an 406 when returning a single object. After hours of troubleshooting I found, that the reason was a boolean field in the table. Servicebuilder generates 2 getter-Method for boolean-Fields (Example: fieldname showAdress produces isShowName and getShowName) and Jackson does not like that.
You can reproduce that by just adding a boolean field in the Sample-Project.

So how can I solve that? How can I prevent ServiceBuilder from that?
I have been trying to expose the same type of spring restful api in Liferay 7.
But failed to get any done.

Is liferay 7 supporting spring restful framework?