扩展一个参数解析器

发送反馈


REST 服务发布机制简述 可知,HTTP 请求在达到 REST 应用对象,交给资源实现类处理的时候,先要解析 HTTP 请求中的参数,然后才会进入业务逻辑进行处理。参数解析的工作由参数解析器(Decoder)进行,即可以实现将请求参数转换为 Java 对象。

SuperMap iServer 中提供的参数解析器有 JsonDecoder 和 XMLDecoder,分别用于解析 Json 格式的参数和 XML 格式的参数。在使用 SuperMap iServer REST 服务时,参数的类型由 HTTP 请求中的 X-RequestEntity-ContentType 请求头和 X-UrlEntity-ContentType 请求头标识,分别标识请求体中的参数的类型和 URI 中的参数的类型。它们为 application/json 时,表明 HTTP 请求中的参数是按 Json 格式组织的,SuperMap iServer 就会使用 JsonDecoder 去解析参数;当为 application/xml 时,SuperMap iServer 就会使用 XMLDecoder 去解析参数。如果 X-RequestEntity-ContentType 或 X-UrlEntity-ContentType 请求头为空,服务器默认用 JsonDecoder 进行参数解析。

如果你在使用 SuperMap iServer REST 服务的时候,想要使用一种新的格式(Json、XML 之外)作为 HTTP 请求中参数的组织形式,就需要对参数解析器进行扩展。SuperMap iServer 提供了良好的参数解析器扩展机制,扩展一个参数解析器的过程可分如下两步:

  1. 实现一个参数解析器类

  2. 将自定义的参数解析器配置到 REST 服务

1. 实现一个参数解析器类

在 SuperMap iServer 的 REST 实现框架中,提供了 com.supermap.services.rest.decoders.Decoder 抽象类用于参数解析器,所有的参数解析器都继承自该类。例如,在 SuperMap iServer 中已提供的 JsonDecoder、XMLDecoder 等。

用户扩展新的参数解析器,既可以继承 Decoder 抽象类,对其中的抽象方法进行实现,也可以继承现有的参数解析器类,改写其中的方法。其中,重要的方法如下表所示,详请参见 JavaDoc 文档:

方法名称 含义
createSupportedMediaTypes() 创建该参数解析器支持的媒体类型列表。即指明能处理什么类型的参数。
toObject(String, Class) 将参数字符串转换成指定类型的 Java 对象。
toList(String, Class) 将参数字符串转换成指定元素类型的 java.util.List 对象。
toMap(String, Map<String, Class>) 将参数字符串转换成指定类型的 java.util.Map 映射集。
toSet(String, Class) 将参数字符串转换成指定元素类型的 java.util.Set 对象。

继承 Decoder 抽象类(或其子类),实现(或重写)上表中的方法,就可以实现一个自定义的参数解析器。

作为示例,这里仅举一个特例来说明扩展参数解析器的过程,对 distance 资源中执行 GET 请求时,对如下特定格式的自定义参数进行解析(该格式仅针对 distance 资源):

point2Ds=[(x= 23.00,y=34.00);(x= 53.55,y=12.66);(x= 73.88,y=12.6)]&unit=RADIAN

注:正常的 Json 格式的参数如下所示:

point2Ds=[{x: 23.00,y:34.00},{x: 53.55,y:12.66},{x: 73.88,y:12.6}]&unit=RADIAN

MyDecoder 的层次结构如下:

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;

/**

 * 针对 distance 资源的扩展参数解析器类。

 *

 * JSON 格式的参数:point2Ds=[{x: 23.00,y:34.00},{x: 53.55,y:12.66},{x: 73.88,y:12.6}]&unit=METER

 * 预期可解析参数: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;

                

                //转换数组。

                //mytypeStr 以“[”开头,“]”结尾时,表示一个 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{

                                //数组中只有一个 Point2D

                                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)) {

                        //当 targetClass 是枚举类型时,按枚举类型解析 mytypeStr

                        //这里 targetClass 是 Unit 时,把 mytypeStr 转化成一个 Unit 枚举对象

            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("字符串" + entryStr + "包含多个:");

                                        }

                                        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("字符串格式不对");

                }

        return mapping;

        }

}

至此,一个特殊的参数解析器类就完成了。你可以在%SuperMap iServer_HOME%/samples/code/ExtendREST 下找到该实例程序的源代码。

MyDecoder 类,编译后,需要打成 Jar 包放到%SuperMap iServer_HOME%/webapps/iserver/WEB-INF/lib 目录下,这里将它打进 extendREST.jar 中。

2. 将自定义的参数解析器配置到 REST 服务

将自定义的参数解析器配置到 REST 服务的流程跟将自定义的表述生成器配置到 REST 服务的流程类似。也是先注册成为 Bean 组件,然后再配置到单个资源或所有资源。

注册 MyDecoder 类为 Bean 组件,需要在 REST 应用配置文件中添加一个<bean/>节点,REST 应用配置文件 iserver-rest-appContext.xml 位于%SuperMap iServer_HOME%/webapps/iserver/WEB-INF 目录下。如下所示,将 MyDecoder 类注册成了 MyDecoder Bean 组件(class 的路径为 MyDecoder 类在 Jar 包中的路径):

<bean id="MyDecoder" class="com.supermap.sample.extendREST.MyDecoder"/>

配置到单个资源在资源配置文件中进行,配置到所有资源,在 iserver-rest-appContext.xml 中的<util:map id="restConfig"/>中,由 key="systemDecoders"的<entry/>节点中进行。

注:iserver-rest-resources.xml、iserver-rest-appContext.xml 位于%SuperMap iServer_HOME%/webapps/iserver/WEB-INF 目录下。

作为本文中这个特殊的参数解析器 MyEncoder,在注册成名为 MyEncoder 的 Bean 组件后,只能配置到 distance 资源。distance 资源的资源配置信息位于 %SuperMap iServer_HOME%/webapps/iserver/WEB-INF/lib/iserver-all-{version}.jar/resource/rest 下的 mappingResources.xml 中。如下所示:

<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>

 

至此,一个参数解析器的扩展就完成了。

重启 SuperMap iServer REST 服务,这时,distance 资源就能接受特定格式的参数

需要提醒的是,本文的开篇曾经介绍过,服务器端对参数解析器的选择是基于 HTTP 请求中 X-RequestEntity-ContentType 和 X-UrlEntity-ContentType 请求头的值的。在示例代码的 createSupportedMediaTypes 方法(即加粗的部分)中我们看到,MyDecoder 参数解析器能解析的参数对应的媒体类型是“application/custom”。所以,在对 distance 资源执行 GET 请求时,需要设置请求头以标识参数的媒体类型,distance 资源中, 参数放在 URI 中,请求体中没有参数,所以设置 X-UrlEntity-ContentType 请求头的值为“application/custom”,X-RequestEntity-ContentType 请求头可以不管。 这时服务器才会选择正确的参数解析器(即 MyDecoder)进行参数(URI 中的参数)解析,从而得出正确的结果。

利用 JavaScript 编写的简单客户端程序如下(请参见“使用 REST API”中的示例介绍):

function getDistance()

{

        //获取 XMLHttpRequest 对象

        var commit=getcommit();

        //以 json 格式的表述格式为例

        var uri="http://localhost:8090/iserver/services/components-rest/rest/maps/世界地图/distance.json";

        //URI 中的参数

        var params1="point2Ds=[(x: 23.00,y:34.00);(x:53.55,y:12.66);(x:73.88,y:12.6)]&unit=METER";

        //请求体中的参数,这里为 null

        var params2=null;

        commit.open("GET",uri+'?'+params1,false,"","");

        //设置 X-UrlEntity-ContentType 为 application/custom,以标识 URI 中的参数的媒体类型

        commit.setRequestHeader('X-UrlEntity-ContentType', 'application/custom');

        commit.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

        commit.send(params2);

        //显示服务器端的响应内容

        alert(commit.responseText);

}

效果如下,SuperMap iServer 正确解析了给定的特殊格式参数,返回正确的结果:

你可以在%SuperMap iServer_HOME%/samples/code/ExtendREST/distance 目录下找到该客户端程序的源代码。