Showing posts with label RESTful. Show all posts
Showing posts with label RESTful. 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

Wednesday, February 4, 2015

Rest Template handle gzip format

Create new converter for Gzip format:

public class GzipMarshallingHttpMessageConverter extends
        AbstractHttpMessageConverter<DLSSeries> {

    public GzipMarshallingHttpMessageConverter() {
        super(new MediaType("application", "json"));
    }
   
    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected DLSSeries readInternal(Class<? extends DLSSeries> clazz,
            HttpInputMessage inputMessage) throws IOException {
        boolean gzip = false;
        HttpHeaders headers = inputMessage.getHeaders();
        List<String> contentEncoding = headers.get("Content-Encoding");

        if (headers != null && contentEncoding != null) {
            for (String contentEncodingStr : contentEncoding) {
                if (contentEncodingStr != null
                        && "gzip".equalsIgnoreCase(contentEncodingStr.trim())) {
                    gzip = true;
                }
            }
        }

        if (gzip) {
            return parseGzipStream(inputMessage.getBody());
        }

        return null;
    }
   
    @Override
    protected void writeInternal(DLSSeries t, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        writeToResult(t, outputMessage.getHeaders(), new StreamResult(
                outputMessage.getBody()));

    }
   
    private DLSSeries parseGzipStream(InputStream inputStreamGzip) {
        DLSSeries series = new DLSSeries();
        List<DLSEpisode> episodeList = new ArrayList<DLSEpisode>();
        try {
            InputStream inputStream = new GZIPInputStream(inputStreamGzip);
            ObjectMapper mapper = new ObjectMapper();
            episodeList = mapper.readValue(inputStream, new TypeReference<ArrayList<DLSEpisode>>() {});
            series.setEpisodeList(episodeList);
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
        }
        return series;
    }

    protected void writeToResult(Object o, HttpHeaders headers, Result result)
            throws IOException {
    }
   
    public String convertStreamToString(InputStream is) throws IOException {
        /*
         * To convert the InputStream to String we use the Reader.read(char[]
         * buffer) method. We iterate until the Reader return -1 which means
         * there's no more data to read. We use the StringWriter class to
         * produce the string.
         */
        if (is != null) {
            Writer writer = new StringWriter();

            char[] buffer = new char[1024];
            try {
                Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                int n;
                while ((n = reader.read(buffer)) != -1) {
                    writer.write(buffer, 0, n);
                }
            } finally {
                is.close();
            }
            return writer.toString();
        } else {
            return "";
        }
    }
   
    /** Tracking system operations. */
    private static final Logger LOGGER =  LoggerFactory.getLogger(GzipMarshallingHttpMessageConverter.class);
}


Create RestTemplate instance and add new converter to it:

        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> messageConverters = new         ArrayList<HttpMessageConverter<?>>();
        messageConverters.add(new GzipMarshallingHttpMessageConverter());

        restTemplate.setMessageConverters(messageConverters);


Using restTemplate, for example:

            ResponseEntity<DLSSeries> response = null;
           
            try {
                response = restTemplate.exchange(
                        url, HttpMethod.GET, entity, DLSSeries.class);
               
                if(response != null && response.getBody() != null) {
                    DLSSeries series = response.getBody();
                   //do more things here
                }
            } catch(RestClientException e) {
                LOGGER.error(e.getMessage());
            }


P/s: change some example classes to your own classes.

Monday, January 5, 2015

Encode and Decode param in Webservice

To encode, we do like this:

        ExternalWebServiceUtil.updateProgram(URLEncoder.encode(programId, "UTF-8"));

And for decoding, use:

        externalService.updateProgram(URLDecoder.decode(programId, "UTF-8"));

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