Showing posts with label JSON. Show all posts
Showing posts with label JSON. Show all posts

Thursday, February 5, 2015

Custom JSON web service in Liferay

We can publish JSON webservice from Liferay portlet or hook by 2 ways (at my knowledge at the time of writing this code).

Way No. 1: using Service builder

 Specify "remote-service="true"" in service.xml like below:

<entity name="Employee" local-service="true" remote-service="true">

and run Liferay->SDK->build-service.
It automatically create some interfaces and classes named: *Service, *ServiceImpl or *ServiceUtil... (These files differ with *LocalService, * LocalServiceImpl or *LocalServiceUtil which are used internally JVM).

JSON service has not exposed yet until you write some functions in *ServiceImpl and run Liferay->SDK->build-service again.

The magic is clearly explained in the websites listed in References. Basically, it will add @JSONWebService to *Service interface. By this way, Liferay can scan and publish function in this interface as an API webservice.

Test it by type: http://your-ip:port/plugin-context-path/api/jsonws. It will list out all your service functions that has been published.

Way No. 2: write custom classes and register to Liferay manually

Liferay portal 6.1 is slightly different with Liferay portal 6.2 in the way it scans service functions.

  • In Liferay 6.1:
Create an interface in your package. One thing should be noted is that this interface has to be suffix by "Service" like: "HelloworldService" and mark with @JSONWebService at class scope definition.

@JSONWebService
public interface HelloworldService {
    public java.lang.String sayHello();
}


Corresponding to Service inferface, Liferay need a "Util" class. It will be the endpoint that catches the request from client.

public class HelloworldServiceUtil {
    public static String sayHello() {
        HelloworldServiceImpl service = null;
        if (service == null) {
            HelloworldServiceImpl invokableLocalService = (HelloworldServiceImpl) PortletBeanLocatorUtil.locate("ws-test-portlet", "hellworld");

            if (invokableLocalService != null) {
                service = invokableLocalService;
            }           
        }
       
        if(service != null) {
            return service.sayHello();
        } else {
            return "null";
        }
    }
}


In this case, HelloworldServiceImpl does not take part in directly to the process of  public JSON service. HelloworldService is used to create signature. HelloworldServiceUtil is to handle request. It calls HelloworldServiceImpl to do the business logic and return the result. As you can see, we can get an instance of "HelloworldServiceImpl" from ext-spring.xml through PortletBeanLocatorUtil.locate().
Noted that functions in HelloworldServiceUtil are all static.

Test it by type: http://your-ip:port/plugin-context-path/api/jsonws.

  • In Liferay 6.2:
Things become simpler. Just create an interface and name it any thing you like.

public interface Helloworld {
    public String sayHello();
}


Implement it by an implementation class:

@JSONWebService
public class HelloworldImpl implements Helloworld {

    @Override
    public String sayHello() {
        return "Hello baby";
    }
}


But put @JSONWebService at implementation class like above.
That's all you have to do.

Both in Liferay 6.1 and In Liferay 6.2, you need add JSON Service servlet in web.xml file:

<filter>
        <filter-name>Secure JSON Web Service Servlet Filter</filter-name>
        <filter-class>com.liferay.portal.kernel.servlet.PortalClassLoaderFilter</filter-class>
        <init-param>
            <param-name>filter-class</param-name>
            <param-value>com.liferay.portal.servlet.filters.secure.SecureFilter</param-value>
        </init-param>
        <init-param>
            <param-name>basic_auth</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>portal_property_prefix</param-name>
            <param-value>jsonws.servlet.</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Secure JSON Web Service Servlet Filter</filter-name>
        <url-pattern>/api/jsonws/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>JSON Web Service Servlet</servlet-name>
        <servlet-class>com.liferay.portal.kernel.servlet.PortalClassLoaderServlet</servlet-class>
        <init-param>
            <param-name>servlet-class</param-name>
            <param-value>com.liferay.portal.jsonwebservice.JSONWebServiceServlet</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JSON Web Service Servlet</servlet-name>
        <url-pattern>/api/jsonws/*</url-pattern>
    </servlet-mapping>


Try and happy coding!
-----
References:

http://www.liferay.com/documentation/liferay-portal/6.1/development/-/ai/json-web-services

http://www.liferaysavvy.com/2013/11/liferay-custom-json-web-services-on.html

http://www.endeios.io/blog/-/blogs/leverage-liferay-service-s-structure-for-you-own-personal-back-end-s-service

Sunday, December 14, 2014

RestTemplate cannot map a single object to a list



ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
resObject = mapper.readValue(response.getBody(), ResponseObject.class);

The problem is that if ResponseObject in server side has only one item set to a list, by default, JAXB will convert this list to a single json object (not an array contains one object as we expected) so the mapper in client side cannot map correctly.

Solution 1: by code


List converters = restTemplate.getMessageConverters();
              for (Object converter : converters) {
                     if (converter instanceof MappingJacksonHttpMessageConverter) {
                           MappingJacksonHttpMessageConverter jconverter = (MappingJacksonHttpMessageConverter) converter;
                           
                           final ObjectMapper mapper = new ObjectMapper();
                             mapper.configure(
                                     DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
                                     true);
                           jconverter.setObjectMapper(mapper);
                          
                     }
              }

Solution 2: by XML configuration:


<!-- Jackson Mapper -->
<bean id="jacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="jacksonObjectMapper" />
    <property name="targetMethod" value="configure" />
    <property name="arguments">
        <list>
            <value type="org.codehaus.jackson.map.DeserializationConfig.Feature">FAIL_ON_UNKNOWN_PROPERTIES</value>
            <value>false</value>
        </list>
    </property>
</bean>
 
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper" />
            </bean>
        </list>
    </property>
</bean>

Hope that helps.

References:
http://stackoverflow.com/questions/14343477/how-do-you-globally-set-jackson-to-ignore-unknown-properties-within-spring