Extending a Decoder |
The REST Service Publishing Mechanism section gives you an introduction to the REST service publishing mechanism. We can know that when the HTTP request arrives at the REST application object, the parameters in the HTTP request need to be parsed before the business logic handles the request. Parameter passing is performed by the Decoder, which can converts request parameters into java object.
The decoders provided by SuperMap iServer include: JsonDecoder for Json format parameter parsing and XMLDecoder for XML format parameter parsing. The parameter types are identified by the X-RequestEntity-ContentType and X-UrlEntity-ContentType request headers in the HTTP request. X-RequestEntity-ContentType and X-RequestEntity-ContentType identifies the type of the request body parameters and X-UrlEntity-ContentType identifies the type of the URI query parameters. If the values of the request headers are application/json, the HTTP request parameters are in Json format and will be parsed with JsonDecoder; If the values are application/xml, SuperMap iServer will parse the parameters with XMLDecoder. If the X-RequestEntity-ContentType or X-UrlEntity-ContentType request header is null, the server will use the default decoder JsonDecoder to parse the parameters.
SuperMap iServer also provides the extension mechanism for the decoder. If users want to use another format for HTTP request parameters, that is, a format other than Json and XML, users need to extend the decoder. The two steps for extending the decoder in SuperMap iServer are as follows:
The REST implementation framework of SuperMap iServer provides the abstract class com.supermap.services.rest.decoders.Decoder, from which all decoders inherit, to parase parameters. The decoders provided by SuperMap iServer include: JsonDecoder, XMLDecoder, etc.
When extending the decoder, users need to inherit from the Decoder abstract class and implement the abstract methods in the class, or users can inherit from an existing decoder and overwrite the methods in it. Below is a list of some important methods involved.
Method | Description |
createSupportedMediaTypes() | Creates the list of media types supported by the decoder. |
toObject(String, Class) | Converts the parameter string into Java object. |
toList(String, Class) | Converts the parameter string into java.util.List object. |
toMap(String, Map<String, Class>) | Converts the parameter string into java.util.Map mapping set. |
toSet(String, Class) | Converts the parameter string into java.util.Set object. |
To construct a custom decoder, inherit from the Decoder abstract class or its child class, and implement or overwrite the methods listed in the above table.
For illustration, here we implement the GET request on the distance resource and parse the custom parameters for the distance resource.
point2Ds=[(x= 23.00,y=34.00);(x= 53.55,y=12.66);(x= 73.88,y=12.6)]&unit=RADIAN
Note: Below is the normal Json foramt parameter.
point2Ds=[{x: 23.00,y:34.00},{x: 53.55,y:12.66},{x: 73.88,y:12.6}]&unit=RADIAN
The hierarchy of MyDecoder:
package com.supermap.sample.extendREST;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.restlet.data.MediaType;
import com.supermap.services.components.commontypes.Point2D;
import com.supermap.services.rest.decoders.Decoder;
/**
* Extend the decoder for the distance resource.
*
* JSON foramt parameter: point2Ds=[{x: 23.00,y:34.00},{x: 53.55,y:12.66},{x: 73.88,y:12.6}]&unit=METER
* Expected parsible parameter: point2Ds=[(x:23.00,y:34.00);(x:53.55,y:12.66);(x:73.88,y:12.6)]&unit=METER
*/
public class MyDecoder extends Decoder{
@Override
protected List<MediaType> createSupportedMediaTypes() {
// TODO Auto-generated method stub
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(new MediaType("application/custom"));
return mediaTypes;
}
@Override
public List toList(String text, Class elementClass) throws Exception {
List result = null;
Object obj = this.toObject(text, Point2D.class);
if(obj == null){
return null;
}
Class eleClz = obj.getClass().getComponentType();
if(eleClz .equals(Point2D.class)){
List list = new ArrayList();
Point2D[] points = (Point2D[])(obj);
for(Point2D point: points){
list.add(point);
}
result = list;
}
return result;
}
@Override
public Map<String, Object> toMap(String str, Map<String, Class> nameClassMapping) {
Map<String, Object> mapping = new HashMap() ;
try{
mapping = (Map<String, Object>)this.toObject(str, Point2D.class);
}catch(Exception e){
e.printStackTrace();
}
return mapping;
}
@Override
public Object toObject(String mytypeStr, Class targetClass) throws Exception {
if(targetClass == null){
throw new NullPointerException("param targetClass can not be null");
}
if(mytypeStr == null){
return null;
}
mytypeStr = mytypeStr.trim();
Object resultObj = null;
//Convert the string
//If mytypeStr starts with “[” and ends with “]”, it is a point2Ds
if(mytypeStr.startsWith("[") && mytypeStr.endsWith("]")){
mytypeStr = mytypeStr.substring(1);
mytypeStr = mytypeStr.substring(0,mytypeStr.length() - 1);
mytypeStr = mytypeStr.trim();
if(mytypeStr .equals("")){
return new Object[0];
}else{
//There is only one Point2D in the array
if(mytypeStr.lastIndexOf("(") == 0){
Point2D[] points = new Point2D[1];
//
Point2D point = (Point2D)this.toObject(mytypeStr, Point2D.class);
points[0] = point;
resultObj = points;
}else{
String[] objValueStrs = mytypeStr.split(";");
Point2D[] points = new Point2D[objValueStrs.length];
for(int i = 0 ; i < points.length ;i++){
String objValueStr = objValueStrs[i];
Point2D point = (Point2D)this.toObject(objValueStr, Point2D.class);
points[i] = point;
}
resultObj = points;
}
}
}else if (Enum.class.isAssignableFrom(targetClass)) {
//If targetClass is an enumeration, parse mytypeStr as an enumeration type
//If targetClass is an Unit, converts mytypeStr into an Unit enumeration object
resultObj = Enum.valueOf(targetClass, mytypeStr);
}else{
Map<String ,String> nameValueMapping = this.getMap(mytypeStr);
if(nameValueMapping == null){
return null;
}
if(targetClass.equals(Point2D.class)){
if(nameValueMapping.size() == 0){
return new Point2D();
}else{
Point2D pp = new Point2D();
String strX = nameValueMapping.get("x");
String strY = nameValueMapping.get("y");
try{
double x = Double.parseDouble(strX);
double y = Double.parseDouble(strY);
pp.x = x;
pp.y = y;
resultObj = pp;
}catch(NumberFormatException e){
e.printStackTrace();
}
}
}
}
return resultObj;
}
@Override
public Set toSet(String arg0, Class arg1) throws Exception {
Set result = null;
Object obj = this.toObject(arg0, Point2D.class);
if(obj == null){
return null;
}
Class eleClz = obj.getClass().getComponentType();
if(eleClz .equals(Point2D.class)){
Set set = new TreeSet();
Point2D[] points = (Point2D[])(obj);
for(Point2D point: points){
set.add(point);
}
result = set;
}
return result;
}
private Map<String,String> getMap(String str){
Map<String, String> mapping = new HashMap<String, String>();
if(str == null){
return null;
}
str = str.trim();
if(str.startsWith("(") && str.endsWith(")")){
str = str.substring(1);
str = str.substring(0,str.length() -1);
str = str.trim();
if(str .equals("")){
return new HashMap();
}else{
String[] entryStr = str .split(",");
for(int i=0;i<entryStr.length;i++){
entryStr[i]=entryStr[i].trim();
if(entryStr[i] .indexOf(':') == -1){
continue;
}
String[] element=entryStr[i].split(":");
if(element.length != 2){
System.out.println("String " + entryStr + "contains multiple: ");
}
if(element[1]!=null){
try {
mapping.put(element[0].trim(),element[1].trim());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}else{
throw new IllegalArgumentException("Invalid string format");
}
return mapping;
}
}
Up till now, a simple decoder is finished. You can find the sample code in %SuperMap iServer_HOME%/samples/code/ExtendREST.
After comipling, the MyDecoder class needs to be packaged into a Jar package and place the package in %SuperMap iServer_HOME%/webapps/iserver/WEB-INF/lib. In this example, the DeEncoder class is packaged into extendREST.jar.
Configuring a custom decoder for the REST service is similar to Configuring the Encoder. Firstly, register the decoder as a Bean compoment. Then, configure for a single resource or all resources.
To register the MyDecoder class as a Bean component, we need to add a node in the REST application configuration file--iserver-rest-appContext.xml, which is located in %SuperMap iServer_HOME%/webapps/iserver/WEB-INF. As shown below, the MyDecoder class is registered as the MyDecoder Bean component:
<bean id="MyDecoder" class="com.supermap.sample.extendREST.MyDecoder"/>
The configuration of the decoder for a single resource should be performed in <util:map id="restConfig"/> of iserver-rest-appContext.xml. It is controlled by the <entry/> node of key="systemDecoders".
Notes: iserver-rest-resources.xml and iserver-rest-appContext.xml are located in %SuperMap iServer_HOME%/webapps/iserver/WEB-INF.
After regisetering MyEncoder as a Bean component named MyEncoder, it can only be configured to the distance resource. The cofiguration information of the distance resource is located in Resource.xml in %SuperMap iServer_HOME%/lib/iserver-all-{version}.jar/config/rest, as shown below.
<resource>
<configID>distance</configID>
<urlTemplate>/maps/{mapName}/distance</urlTemplate>
<resourceType>ArithmeticResource</resourceType>
<implementClass>com.supermap.services.rest.resources.impl.DistanceResource</implementClass>
<extensionEncoderBeanNames></extensionEncoderBeanNames>
<extensionDecoderBeanNames>MyDecoder</extensionDecoderBeanNames>
</resource>
Up till now, a custom decoder is finished.
Restart the service to let the distance resource accept parameters of specific format.
Please note that at the beginning of this page, it is introduced that the server chooses the decoder based on the values of the RequestEntity-ContentType and X-UrlEntity-ContentType request headers in the HTTP request. From the createSupportedMediaTypes method in the Sample Code, we know that MyDecoder can parse parameters in application/custom. Therefore, when implementing the GET request on the distance resource, the request header needs to be set to identify the media type of the parameter. Since the parameters are placed in the URI, not the request body, we need set the value of the X-UrlEntity-ContentType request header to application/custom, with the value of the X-RequestEntity-ContentType request header unchanged. Then the server will choose the MyDecoder to parse the parameters in the URI and return the correct result.
Using JavaScript to write client program, as shown below (please refer to Sample Overview in the Using REST API section):
function getDistance()
{
//Get the XMLHttpRequest object
var commit=getcommit();
//Set the output format to json
var uri="http://localhost:8090/iserver/services/components-rest/rest/maps/WorldMap/distance.json";
//URI query parameters
var params1="point2Ds=[(x: 23.00,y:34.00);(x:53.55,y:12.66);(x:73.88,y:12.6)]&unit=METER";
//Request body parameters. Here it is null.
var params2=null;
commit.open("GET",uri+'?'+params1,false,"","");
//Set X-UrlEntity-ContentType to application/custom to indicate the media type of the URI query parameters
commit.setRequestHeader('X-UrlEntity-ContentType', 'application/custom');
commit.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
commit.send(params2);
//Display the response on the server
alert(commit.responseText);
}
SuperMap iServer correctly parses the parameter of a special format and returns the format as shown below:
You can find the source code for the client program in %SuperMap iServer_HOME%/samples/code/ExtendREST/distance.