Tuesday, November 12, 2013

Liferay Custom JSON Web Services on Multiple Tables

Liferay Custom JSON web services on Multiple Tables


Objective:

Create JSON web services for combination of multiple tables means prepare JSON web services so that data should come from multiple tables which are related each other.

Download CustomJsonWebservices portlet from following location

You can find source and war file


Note: 

Portlet developed in Liferay 6.1GA2 EE version
If you want deploy in CE version you just do changes in liferay-plugin-package.properties

Liferay 6.1 EE version

name= CustomJsonWebservices
module-group-id=liferay-ee
module-incremental-version=1
tags=
short-description=
change-log=
page-url=http://www.liferay.com
author=Liferay, Inc.
licenses=EE
liferay-versions=6.1.20

Liferay 6.1 CE version

name = CustomJsonWebservices
module-group-id=liferay
module-incremental-version=1
tags=
short-description=
change-log=
page-url=http://www.liferay.com
author=Liferay, Inc.
licenses=LGPL
liferay-versions=6.1.1

Procedure for deploy portlet:

You can use war file and directly place in your portal deploy folder and test or you can also use source to deploy portlet.
Once portlet is deployed successfully insert data in the following table as shown in screens

json_employee



json_address



And test web service URLs

Liferay provide JSON web services so that we can use those services in other systems.

Liferay providing two types of web services SOAP based and REST bases web services.

Liferay JSON web services come under REST based web services.

Here we are talking about only rest based web services i.e. liferay JSON web services.

Liferay have built in ability can generate JSON web services by default when we make remote-service true for service builder.

But the services which created by default we can apply on only one table means data can served from only one table or web services related to single entity.

But in real scenario the default web services not enough to full fill real requirement there we have write our custom methods to produce JSON data. This pretty easy in liferay because liferay also provides write custom methods for JSON web services in pluin portlet.

Note:

Liferay have provided to generate service layer using service builder. We will use servce builder to generate service layer classes and methods

Steps to produce Custom JSON web services in Liferay Plugin portlet environment

The Following are the steps write custom methods to produce JSON web services:

Step: 1

Create Plugin portlet in Liferay using liferay IDE and portlet is liferay MVC portlet.
Step: 2

Create service bolder for plugin portlet

Open service.xml file and define data base tables and put remote-service is true. When we put remote service true then it will create default JSON web services.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.1.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_1_0.dtd">
<service-builder package-path="com.meera.jsonwebservices.db">
            <author>E5410</author>
            <namespace>JSON</namespace>
            <entity name="Employee" local-service="true" remote-service="true">
                        <column name="emplyeeId" type="long" primary="true" />
                        <column name="emplyeeName" type="String" />
                        <column name="employeeDesignation" type="String" />
                        <order by="asc">
                                    <order-column name="emplyeeId" />
                        </order>
            </entity>
            <entity name="Address" local-service="true" remote-service="true">
                        <column name="Id" type="long" primary="true" />
                        <column name="emplyeeId" type="long" />
                        <column name="employeeAddress" type="String" />
                        <order by="asc">
                                    <order-column name="emplyeeId"/>
                        </order>
                        <finder name="emplyeeId" return-type="Collection">
                                    <finder-column name="emplyeeId" />
                        </finder>
            </entity>
</service-builder>

Step: 3

Now we need add JSON Web service servelet in web.xml of porltet. So that it will get web services exposing ability.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
            <display-name>CustomJsonWebservices-portlet</display-name>
            <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>0</load-on-startup>
       </servlet>
       <servlet-mapping>
         <servlet-name>JSON Web Service Servlet</servlet-name>
         <url-pattern>/api/jsonws/*</url-pattern>
       </servlet-mapping>
            <jsp-config>
                        <taglib>
                                    <taglib-uri>http://java.sun.com/portlet_2_0</taglib-uri>
                                    <taglib-location>
                                                /WEB-INF/tld/liferay-portlet.tld
                                    </taglib-location>
                        </taglib>
                        <taglib>
                                    <taglib-uri>http://liferay.com/tld/aui</taglib-uri>
                                    <taglib-location>/WEB-INF/tld/aui.tld</taglib-location>
                        </taglib>
            </jsp-config>
           
</web-app>


Step: 4

Run service builder using ant build-service or in eclipse you can run this from ant view.

Step: 5

Writing Custom methods for produce JSON data

We need to decide for which entity we have to provide custom JSON services .find appropriate entity and write method XXXServiceImpl.java this is under package of
 you-base-package.service.impl package of your source.

Here XXX is entity name which is specified in service.xml

In our example I have implemented in EmployeeServiceImpl.java here I am getting employee object by employeeId. This method return employee object. In liferay any object automatically sterilizes and produces as JSON data.

The following is example for code


public class EmployeeServiceImpl extends EmployeeServiceBaseImpl {   
            public  com.meera.jsonwebservices.db.model.Employee getEmployee(
                                    long emplyeeId)
                                    throws com.liferay.portal.kernel.exception.PortalException,
                                                com.liferay.portal.kernel.exception.SystemException {
                                    return EmployeeLocalServiceUtil.getEmployee(emplyeeId);
                        }
}


Once we completed writing custom method in XXXServiceImpl.java

Then we need to run service builder using ant build-service command or from eclipse ant view you can run same command

Step: 6

Now finally deploy the portlet into server by using ant deploy command or from ant view you can use same command

Now we are ready with custom JSON web services

Note:

For each modification in XXXServiceImpl.java or in service layed we have run ant build-service command  later we have to deploy the portlet using ant deploy then only change will be applied to the services.
  
Accessing JSON web services

To access the services we need to use following URL pattern



Server:

 It’s your domain name or host name if you are in local the its “localhost”

Port:

It’s your application server port number.

plugin-context:

 it’s our plug-in portlet context name its simple as portlet name

api/jsonws:

This is path for call JSON Web Service servlet this is configured in web.xml

service-class-name:

 service-class-name is generated from the service’s class name by removing the Service or ServiceImpl suffix and making it lower case.This is our service class name where we implemented custom method or its simple a entity name which is in service.xml. In our example we implemented our method in EmployeeServiceImpl.java
Finally we have to use in URL as employee when we observe it’s just entity name in service.xml

service-method-name:

Service-method-name is generated from the service’s method name by converting its camel case to lower case and using dashes (-) to separate words

Finally our Complete URL like this for our Example:





Plugin context path
CustomJsonWebservices-portlet
Web service servelet path
/api/jsonws
Service Class Name
EmployeeServiceImpl.java converted as employee
Service Method Name
getEmplyee(--) converted as get-employee



Passing parameters and its values:

We can pass parameters in two ways

Query String

URL pattern and separated param name and value by slash(/)

Complete URL example with query string




Complete URL by URL pattern:




For Our Example Complete URL to access employee data by passing employee id is following like this



OR


result:

{"employeeDesignation":"SoftwareEngineer",
"emplyeeId":1,"emplyeeName":"meera"}



Note:

 Some time its ask Authentication so we should pass admin credentials as URL headers

With authentication The URL like follow

OR


result:

{"employeeDesignation":"SoftwareEngineer",
"emplyeeId":1,"emplyeeName":"meera"}



Note:

 All these URLs you can use from any browser and see the JSON data. Here sometimes browsers not allows @ or # symbols in URL then we have to make this as URL encoding type characters

For encode URL and test in browse use following link and encode


Encoded URL:


OR



Well as of now we just done created Custom service and its calling

Now we will see another way to call same web service method in JSON Web Services Invoker

JSON web service Invoker:

JSON Web Services Invoker is one of the mechanism to call some complex web services very easy way.

Here we can call JSON web service Invoker by following URL pattern



URL explanation is same like i explained previous section here we need to remember is to call invoker we have to use path /api/jsonws/invoke

This call take only one parameter i.e. cmd the value of this parameter is JSON Map.

Here we need to pass parameter as query string only like follow



Our complete URL for example is




What is JSON Map?

JSON Map is simple JSON object it contains key and value here value is another JSON object which contains key and value

In JSON map key will be used as service call path and value is parameters which passing to service calls.
The same example we discussed above we can do like this

Take the example get employee data by passing id.

Our Normal URL call is as follows




In above URL service call is    employee/get-employee/  and parameter is employeeId and its value is 1

Now the following is JSON map for above


{
"/employee/get-employee": {
"emplyeeId": 1,
}
}


Here key is used as Service call and value is another json object contains parameters and its value.

Now call our complete web service call using JSON web service Invoker as follows



result:

{"employeeDesignation":"Software Engineer",
"emplyeeId":1,"emplyeeName":"meera"}




This is way we have to pass JSON map to JSON web service invoker.

We can assign data as some reference object like following .here we have to specify $ before variable


{
"$emp=/employee/get-employee": {
"emplyeeId": 1,
}
}


$emp is reference variable




In above call we will get JSON data have all columns of Employee Table

Fetching required columns from JSON Web service invoker means filtering columns


{
"$emp[emplyeeName,emplyeeId]=/employee/get-employee":
{
"emplyeeId": 1,
}
}


The following is complete web service call to filter columns using JSON web service invoker



result:

{"emplyeeId":1,"emplyeeName":"meera"}



As of now we just call custom web service which is related to one entity or one table
Now we will see how to fetch JSON data so that data comes from multiple Tables
Some times in real requirement the data should fetch from multiple tables.

To make use of custom method we can produce JSON web services so that data come from multiple tables.

Understanding Real Scenario:

I have one employee each employee have multiple addresses.

Here I have two tables Employee and Address tables.

If we generate JSON web services from liferay it will serve only one table data for service. But here data stored in two tables.

So now we have to produce JSON services so that data should come from Employee Tables and Address table

Employee Table:

employeeId
employeeName
employeeDesignation
1
meera
Software Engineer
2
prince
Architect
3
savvy
Developer




Address Table:

Id
emplyeeId
Address
1
1
Hyderabad
2
1
Hong Kong
2
1
Landon




When you observe above data employee meera have 3 addresses

This is where we need to apply custom JSON web services.

Assume my data should be like as followed structure


{
        name:”meera”,
       addresess:[
                          “Hyderabad”,”Landon”,”Hong Kong”
                        ]
}



We have two options

Complete manual preparation of JSON data

Use JSON Web service Invoker Nested calls

Complete manual preparation of JSON data:

In the manual preparation we will prepare JSON structure and will produce data.

In general List and any java object type can be serialize and will be produces as JSON data in liferay web services.Some scenarios some complex object will not be sterilize.If some thing complex structure then we have to prepare JSON data and we will produce as web services.

Default sterilization for list type and any object type:

In this scenario when we return some java object in custom method it will be automatically converted as JSON data. Here we need not do like this

Example for  Java Object to JSON data or JSON Object:


public class EmployeeServiceImpl extends EmployeeServiceBaseImpl {
           
            public  com.meera.jsonwebservices.db.model.Employee getEmployee(
                                    long emplyeeId)
                                    throws com.liferay.portal.kernel.exception.PortalException,
                                                com.liferay.portal.kernel.exception.SystemException {
                                    return EmployeeLocalServiceUtil.getEmployee(emplyeeId);
                        }
}


In above custom method getEmplyee return Employee objects type. Here we need not to serialize the data and it will be converted as JSON data by default this is default behavior of liferay web services.

The following is URL to access method


OR


result:

{"employeeDesignation":"Software Engineer",
"emplyeeId":1,"emplyeeName":"meera"}



Note:

In the above custom method return Employee object but finally object convereted as JSON data.

 Example for  List to JSON data


public class AddressServiceImpl extends AddressServiceBaseImpl {          
            public  List<com.meera.jsonwebservices.db.model.Address> getAddressList(long emplyeeId)
                                    throws com.liferay.portal.kernel.exception.PortalException,
                                                com.liferay.portal.kernel.exception.SystemException {
                                    return AddressUtil.findByemplyeeId(emplyeeId);
                        }
}


In the above custom method return list of Address objects for particular employee id.
Here list will be converted as JSON data by default

The following is URL to call the above service


OR


result:

[{"employeeAddress":"Hyderabad","emplyeeId":1,"id":1},
{"employeeAddress":"Hong Kong","emplyeeId":1,"id":2}]


Here Address is our service class and getAddressList is custom method to give list of address objects for particular employee.
Here List will be converted as JSON array simple.

Some scenarios which data is complex then default sterilization not work all times when default sterilization not work then we will prepare JSON data manually in custom method. Scenarios like list have map objects and map have list objects.

Example for specialized structure:

Take our previous scenario one employee have many address but my data structure should be like follows


{
        name:”meera”,
       addresess:[
                          “Hyderabad”,”Landon”,”Hong Kong”
                        ]
}


We can’t produce this same JSON data by default here we have to prepare JSON data manually from java objects.

The following is Example:


public class EmployeeServiceImpl extends EmployeeServiceBaseImpl {
            public  JSONObject getEmployeeManualJsonData(
                                    long emplyeeId)
                                    throws com.liferay.portal.kernel.exception.PortalException,
                                                com.liferay.portal.kernel.exception.SystemException {
                       
                        JSONObject employeeData=JSONFactoryUtil.createJSONObject();
                        JSONArray addressArray=JSONFactoryUtil.createJSONArray();
                        Employee      empObject=EmployeeLocalServiceUtil.getEmployee(emplyeeId);
                        employeeData.put("name",empObject.getEmplyeeName());
                        for(Address empaddress:AddressUtil.findByemplyeeId(emplyeeId)){
                                    addressArray.put(empaddress.getEmployeeAddress());
                        }
                        employeeData.put("addresess", addressArray);
                       
                                    return employeeData;
                        }
}



Here service class is EmployeeServiceImpl.java it will be converts as employee in URL and method is getEmployeeManualJsonData it will be converted in URL as get-employee-manual-json-data

The following is URL to access above service


OR


result:

{"name":"meera","addresess":["Hyderabad","Hong Kong","Landon"]}


Note:

 liferay providing JSONFactoryUtil class from this we can create JSONArray and JSONObject.

By using above classes we can prepare JSON data manually in custom web service method.

Nested JSON web service Invoker:

We already know JSON Web service Invoker is one of the way to call web services in liferay.
Nested JSON web service invoker has ability serve data from multiple tables.

By using this we can get data from multiple tables which are related to each other.

Same above scenarios I want data fetch all addresses for employee by pass employeeId.
We can call following web service invoker to get employee address. From nested web service invoker we can achieve that.

The following is JSON map for fetch employee addresses


{
"$employee =/employee/get-employee":
{
"emplyeeId": 1,
"$address = /address/get-address-list": {
"@emplyeeId" : "$employee.emplyeeId"
}
 }
}


@emplyeeId  at which parameter we pass to service call

$employee.emplyeeId   we will get employeeId from $employee reference object.

emplyeeId is same as column name  of entity which we mention in service.xml.

Observer above JSON Map:

First we need to fetch employee object and referenced by variable called $employee we use this employee object to get employeeId and this id will be passed to /address/get-address-list service call.

In the first service call i.e. /employee/get-employee   we will get employee object and referenced by variable, in the next service call i.e. /address/get-address-list we use this reference variable to get employeeId and that will passed as parameter value.

Here we are calling two service calls within one JSON Map object that is why we call it as Nested JSON Web Service Invoker.

Note:

Here all parameters which we passed in JSON map should be same as parameter names which we used in custom method implementation in XXXServiceImpl class.

The final web service call using Nested JSON web service invoke



result:

{"emplyeeId":1,"address":[{"employeeAddress":"Hyderabad","emplyeeId":1,"id":1},
{"employeeAddress":"Hong Kong","emplyeeId":1,"id":2},
{"employeeAddress":"Landon","emplyeeId":1,"id":3}],
"emplyeeName":"meera","employeeDesignation":"Software Engineer"}


Filter columns in Nested JSON web service call



result:

{"emplyeeId":1,"address":[{"employeeAddress":"Hyderabad","emplyeeId":1,"id":1},
{"employeeAddress":"Hong Kong","emplyeeId":1,"id":2},
{"employeeAddress":"Landon","emplyeeId":1,"id":3}],"emplyeeName":"meera"}


In the above call we only get employeeId and employeeName from employee object.

Now calling JSON web service with portal context:

As of now we have used plugin portlet context for calling web services. We can also call web services using portal context/

The following is pattern to call from portal context:
.


Note:
Conveniently, requests sent this way can leverage the user’s authentication in his current portal session. Liferay’s JavaScript API for services calls plugin services using this method.
The following is call web service from plugin context.




Note:

This calls the plugin’s service in a separate web application that is not aware of the user’s current session in the portal. As a result, accessing the service in this manner requires additional authentication. You might use this for batch services or other requests that don’t require context.

The following are dependent properties related to JSON web service in liferay


jsonws.web.service.invalid.http.methods=DELETE,POST,PUT

jsonws.web.service.strict.http.method=true

json.service.auth.token.enabled=true
json.service.auth.token.hosts.allowed=255.255.255.255
json.deserializer.strict.mode=false

jsonws.web.service.public.methods=*


Note:

Please consider all above properties in your portal.properties file. If something you  want change you can use portal-ext.properties file to override.

Important points:
  • Liferay provides two kinds of web services SOAP and REST.
  • JSON web services is part REST bases web services which partially follows rest specifications.
  • To implement service layer in liferay we will use service builder to and if we use remote- service is true then we can get data base services and JSON web services.
  • Liferay web services serve only one table data by default.
  • If we want get data which is related multiple tables or some specialize requirement we have to implement customer web services.
  • To implement any custom services we will use XXXServiceImpl.java class.
  • We can access JSON web services from simple URL pattern URL include plugin context, service class name, service method name and its parameters used by service method.
  • We can pass parameter as query string or normal URL pattern so that each param and values should be separated by slash (/)
  • We can access JSON web services using plug-in portlet context and portal context too. But difference is when we call from plug-in context we need implement our own authentication mechanism for services. If we use portal context then it will use authentication mechanism which provided by liferay portal.
  • JSON web service invoker is way to call web services which will fetch data from multiple tables.
  • To fetch data from multiple tables we have to implement custom method so that custom method prepares JSON data manually or we can use Nested JSON web service invoker.
  • By default all java objects, list and map can be sterilize automatically and it will be produced as JSON data. Some scenarios if data has complex structure then we have to prepare JSON data manually.
  • To prepare JSON data manually we can JSONFcatoryUtil class this can create JSONObject and JSONArray.
Screen 1:

The following screen depicts call JSON web services in browser and result will be JSON data.


Note:

In the URL host name and port number change according to you environment and in the web service URL pass your liferay admin username and password.





Reference Links:


1 comment :

  1. Thanks for sharing this excellent topic, and explaining each step quite well. You should also look at that tool for the encoding/decoding
    url-decode.com/
    that tool also contains 100+ web utilities as well for the users. It will definitely help the users as well.

    ReplyDelete

Recent Posts

Recent Posts Widget

Popular Posts