So far we have a way to generate the DataSource code based on a XML file describing the DataSource, making declaration of DataSources on client side (almost) a breeze. Now we need a way to either generate the server side code which is called by the DataSource or to create a generic one which can be used for all DataSources.

Our generated code is using RestDataSource on client side, which implements the 4 core DataSource operations (add, remove, update, fetch) using a simple protocol of XML or JSON requests and responses sent over HTTP. This being completed already, let’s turn to server side and see how can we make things a bit better when generating the response XMLs we need to return when these operations are called on server side.

The server provided with smartclient handles all DataSource communication using a servlet called IDACall. The server will instantiate a server side object which will take care of retriving the data (from datasource, computing it, etc) and returning it back to servlet.Then it will serialize it and will send it to the client side DataSource object.

Checking the examples of smartgwt ee and smartclient enterprise and checking the provided java docs, seems like the servlet will create a DSRequest object which will be handled to the method called for fetch, add, remove, update. The response will be returned as a DSResponse object.

We’ll recreate these objects on server and we’ll try to write a servlet which will try to successfully replace the one provided with the smartclient server when working with DataSources, not functionally, but maybe as how easy requests can be processed on server side.

Let’s get first some information about the client side RestDataSource.

  • The format of data sent to the server is determined by the dataProtocol specified for the operation. Request data is sent as parameters if the format is specified as “getParams” or “postParams”. We already set this in the DataSource which is extended by the client side DataSource we generate with generator as described in previous post.
  • There is a standardized way to send back the answer. For example for fetch it looks like the following:
    <response>
    	<status>0</status>
    	<startRow>start row number</startRow>
            <endRow>the end row number</endRow>
            <totalRows>total number of rows</totalRows>
            <data>
    		<record>
    			<field1>value</field1>
    			<field2>value</field2>
    		</record>
    		<record>
    			<field1>value</field1>
    			<field2>value</field2>
    		</record>
                   ... 75 total records ... 
           </data>
    </response>

considering this and the information provided in javadocs DSRequest should at least contain , the DataSource id of DataSource which makes the request on client side, start row, end row, what kind of operation is requested, the field after which the sort is made, and a hash map with field value pair as a criteria of the request. For easy handling, we also include HttpServletRequest, HTTPServletResponse and HTTPSession objects of the servlet. We end up with something pretty much like:

import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class DSRequest
{
	HttpSession session;
	HttpServletResponse response;
	HttpServletRequest request;

	private static String OPERATION_FETCH = "fetch";
	private static String OPERATION_UPDATE = "update";
	private static String OPERATION_REMOVE = "remove";
	private static String OPERATION_ADD = "add";

	String dataSourceName;
	String operationType;

	HashMap criteria;

	int startRow;
	int endRow;
	boolean paged;

	String sortBy;

	public DSRequest(String dataSourceName, String opType)
	{
		this.dataSourceName = dataSourceName;
		this.operationType = opType;
		endRow = -1;
		startRow = -1;
		paged = true;
	}

	public HttpSession getSession()
	{
		return session;
	}

	public void setSession(HttpSession session)
	{
		this.session = session;
	}

	public HttpServletResponse getResponse()
	{
		return response;
	}

	public void setResponse(HttpServletResponse response)
	{
		this.response = response;
	}

	public HttpServletRequest getRequest()
	{
		return request;
	}

	public void setRequest(HttpServletRequest request)
	{
		this.request = request;
	}

	public String getDataSourceName()
	{
		return dataSourceName;
	}

	public void setDataSourceName(String dataSourceName)
	{
		this.dataSourceName = dataSourceName;
	}

	public String getOperationType()
	{
		return operationType;
	}

	public void setOperationType(String operationType)
	{
		this.operationType = operationType;
	}

	public HashMap getCriteria()
	{
		return criteria;
	}

	public void setCriteria(HashMap criteria)
	{
		this.criteria = criteria;
	}

	public int getStartRow()
	{
		return startRow;
	}

	public void setStartRow(int startRow)
	{
		this.startRow = startRow;
	}

	public int getEndRow()
	{
		return endRow;
	}

	public void setEndRow(int endRow)
	{
		this.endRow = endRow;
	}

	public boolean isPaged()
	{
		return paged;
	}

	public void setPaged(boolean paged)
	{
		this.paged = paged;
	}

	public String getSortBy()
	{
		return sortBy;
	}

	public void setSortBy(String sortBy)
	{
		this.sortBy = sortBy;
	}

	public boolean isAdd()
	{
		return OPERATION_ADD.equals(operationType);
	}

	public boolean isRemove()
	{
		return OPERATION_REMOVE.equals(operationType);
	}

	public boolean isUpdate()
	{
		return OPERATION_UPDATE.equals(operationType);
	}

	public boolean isFetch()
	{
		return OPERATION_FETCH.equals(operationType);
	}
}

The DMI invocation passes the result back to the servlet wrapped in a DSResponse object. This DSResponse object should contain at least status of the response, start row, end row, total rows, data itself and a list of errors. Wrapping up, we get something pretty much like:

import java.util.ArrayList;
import java.util.List;

public class DSResponse
{
	public static int STATUS_FAILURE = -1;
	public static int STATUS_LOGIN_INCORRECT = -5;
	public static int STATUS_LOGIN_REQUIRED = -7;
	public static int STATUS_LOGIN_SUCCESS = -8;
	public static int STATUS_MAX_LOGIN_ATTEMPTS_EXCEEDED = -6;
	public static int STATUS_SERVER_TIMEOUT = -100;
	public static int STATUS_SUCCESS = 0;
	public static int STATUS_TRANSPORT_ERROR = -90;
	public static int STATUS_VALIDATION_ERROR = -4;

	int status;
	long startRow;
	long endRow;
	long totalRows;
	Object data;
	List errors;

	public int getStatus()
	{
		return status;
	}

	public void setStatus(int status)
	{
		this.status = status;
	}

	public long getStartRow()
	{
		return startRow;
	}

	public void setStartRow(long startRow)
	{
		this.startRow = startRow;
	}

	public long getEndRow()
	{
		return endRow;
	}

	public void setEndRow(long endRow)
	{
		this.endRow = endRow;
	}

	public long getTotalRows()
	{
		return totalRows;
	}

	public void setTotalRows(long totalRows)
	{
		this.totalRows = totalRows;
	}

	public Object getData()
	{
		return data;
	}

	public void setData(Object data)
	{
		this.data = data;
	}

	public List getErrors()
	{
		return errors;
	}

	public void setErrors(List errors)
	{
		this.errors = errors;
	}

	public DSResponse()
	{
		status = STATUS_SUCCESS;

		errors = new ArrayList();
       		startRow = 0;
        	endRow = 0;
        	totalRows = -1;
	}

	public DSResponse(int status)
	{
		this.status = status;

		errors = new ArrayList();
        	startRow = 0;
        	endRow = 0;
        	totalRows = -1;
	}
}

The DMI object will return it’s answer in form of DSResponse object. The data member will contain the data part of the answer – the record or the records – returned to the client side DataSource while the additional fields of the DSResponse help the servlet build the REST response required by the client side DataSource.

Having these set up, now we need a servlet which gets the request from the DataSource, creates a DSRequest object and fills it with information passed in parameter of the request / post parameters by the client side DataSource object, identifies the XML describing the DataSource, instantiate the class defined in the DataSource as responsible for DMI operations, then using reflection calls one of add(), remove(), fetch(), update() methods of the instantiated object, which will return a DSResponse object to the servlet. Using this DSResponse object, the servlet builds the XML response using the information from the object and also using reflection to generate the data part. If written carefully, we can reuse the code we used in DataSource generator to load DataSources and their fields, except in this case probably the best would be to load them at server startup and maybe create a mapping between the id of the DataSource and the DataSource object itself, for easier and faster referencing.

The steps of doing this are:

  1. First we need to make a servlet and map it to the URL we declared in the generator (“/DMI” or anything else). This servlet will be used for all our DataSources.
  2. When servlet initializes, it should go over the defined DataSource xmls and should load them one by one, and create a mapping between the DataSource object and its id, which would make real fast lookup of DataSource by its ID.
  3. On incoming request, should parse the request and get all _dataSource, _operationType, _startRow, _endRow, _sort_by parameters and use these to build the DSRequest object. Then should get all the other parameter value fields and cross-check them against the datasource fields, and if they are found,create a HashMap with their name / value as criteria of the operation, and store it also in the DSRequest object.
  4. Handle the operation – instantiate the DMI object and call the operation method (add(), fetch(), remove(), update() ) and get back the DSResponse object.
  5. Build the XML response according to the operation and information stored in DSResponse, and serialize the data part of the DSResponse using a generic XML serializer based on reflection, cross-referencing the name of fields with the fields declared in the DataSource xml.
  6. Send back the answer to DataSource.

Some notes on building the response:

  • it will be easier if you write a generic XML serializer which can chew any kind of object (using reflection to get the object’s fields) and throw out the fields of the object in form of XML.
  • make sure you can serialize both simple object and a collection of objects. By doing this, DSResponse won’t change, and there is no much difference in a DMI object related to returning a record or a list of records to DataSource. If a collection is returned, simply serialize each member of the collection using the serializer I’ve discussed about.
  • do take in consideration the status flag of DSResponse. There are couple of standard values predefined as constants. In case of failure and success serialise the objects normally with status field and so (however on error there is no reason to serialize the data itself) but in case of authorization errors, instead of returning the status response, return the various markers defined by smartclient. The server side will detect them and will automatically trigger relogin flow. There will be a next article with more detail about implementing the relogin flow using SmartGWT.

You must be logged in to post a comment.