文章将通过基于 Eclipse 的 IDE,来开发 Worklight 支持的 HTTP 和 SQL 适配器;并且演示测试适配器的方法,最后,通过集成应用、适配器和 Web Service 来体现适配器在开发移动应用中的重大作用。
Worklight 中适配器的架构
WorklightWorklight作为一个移动程序的开发平台,通过三种模式(Web、混合、本地)支持了不同需求的客户端开发,但是面临的一个现实问题是如何和已有的系统进行良好的集成。适配器(adapter)是平台的服务器端组件,作为一个传输层负责Worklight 服务器和不同企业级后台的连接。通过平台支持的 HTTP 和 SQL 适配器,客户端应用可以与原有系统方便的进行数据访问,这减少了重复的开发工作。
适配器由三部分组成:基于 XML 的配置文件,用于定义适配器的类型和提供的方法;基于 JS 的方法实现文件,通过 Mozilla Rhino 解析器实现对不同后台的访问;基于 XSL 文件的数据转化规则,在 HTTP 适配器中存在,用于将获得的数据按照一定规则进行转化。
Worklight支持的适配器提供了如下特性:
- 快速开发:开发者可以使用便捷而强大的服务器端 JavaScript 创建简洁易读的代码,用于和不同后台程序的集成。
- 只读操作和事务操作:平台通过适配器支持对后台系统的只读和事物操作。
- 安全:适配器使用灵活的认证机制创建与后台的连接,使用的用户可以是系统用户也可以是事务中操作的用户。
- 可扩展性:适配器通过 cache 机制减少了与后台系统的交互,并且通过配置,限定和后台系统建立的连接数。
- 数据透明:适配器提供了获取后台数据的统一接口,这样开发者在获取数据时就不需要关注数据源、格式和传输协议信息。
图 1. 适配器架构
图 1 描述了适配器和前台的 app 以及后台系统间数据交互的过程,具体的流程如下:
- 适配器暴露一系列的方法,称之为 procedures,前台 app 通过 Ajax 方式调用这些过程。
- 这些方法从后台获取相应信息。
- 后台系统返回的数据格式如下
- 如果格式为 JSON,Worklight服务器直接返回。
- 如果格式不是 JSON,服务器将数据格式转化为 JSON 后,再返回。
- 这些方法将获得的 JSON 数据进行业务处理,将最终结果返回给 app。
基于 Eclipse 的适配器开发
根据适配器连接的后台种类,它被分为 HTTP 适配器和 SQL 适配器。在实际的应用中 SQL 适配器用于连接不同的后台数据库,并通过 SQL 语句或存储过程进行访问。适配器目前支持 MySQL 和 Oracle 的数据库,并且在几个月后将支持 DB2 数据库。HTTP 适配器支持访问 soap 服务和 rest 服务,并且可以根据指定的 XSL 文件对获得的数据片段进行转化,获得用户需要的数据和格式。
为了更好的演示不同适配器的作用和相互合作的方法,文章将构建如下场景:
- 首先创建 SQL 适配器,并且提供对于 SQL 语句和存储过程的访问。
- 然后创建 HTTP 适配器,用于访问由 Axis2 框架构建的 Web Service。
- 通过 Eclipse 提供的测试环境,对于前面创建的适配器进行测试,并且解释测试返回信息的含义。
- 最后通过开发一个应用,并且整合上述的适配器,完成一个完整的功能。
adapter-jndi-name = ${custom-db.1.jndi-name} custom-db.1.relative-jndi-name = jdbc/worklight_adapter custom-db.1.driver = com.mysql.jdbc.Driver custom-db.1.url = jdbc:mysql://localhost:3306/worklight_adapter custom-db.1.username = root custom-db.1.password = root
在安装有Worklight 插件的 Eclipse 下开发适配器通过下面的步骤可以完成:
新建工程和 SQL 适配器:
- 点击 File-> New -> Project -> Worklight -> Worklight Project
- 输入工程名称:HelloAdapterProj,点击确认,完成工程创建
- 点击 File-> New -> Other -> Worklight -> Worklight Adapter
- 输入应用名称:MySQLAdapter,选择 HelloAdapterProj 作为工程,在适配器类型中选择 SQL Adapter,点击确认,完成 SQL 适配器创建
创建完毕的适配器其文件结构如下:
- MySQLAdapter.xml:在文件中定义了很多的属性,比如适配器在Worklight 控制台中显示的名称和简单描述,最大的连接数等等,其中最重要的是下列两个参数:
- dataSourceJNDIName 定义了适配器所连接的 JNDI 的名称,具体的配置将在下面叙述
- procedure 定义适配器向外提供的方法名称(需要通过 .js 文件实现)
- MySQLAdapter-impl.js:在文件中实现了两个方法,getAllCountries 用于调用数据库中的存储过程;getOneCountryID 通过调用 SQL 语句获取相应的信息
对于适配器需要的 JNDI 参数,可以通过修改 Worklight 的配置文件实现。在 Worklight 框架中存在一个默认的配置文件 default.worklight.properties,用户可以通过修改 worklight.properties 文件来覆盖默认配置文件中的一些属性。清单 1 描述了为连接本地的 SQL 数据库,在 worklight.properties 添加的配置信息:
清单 1. JNDI 配置
- adapter-jndi-name = ${custom-db.1.jndi-name}
- custom-db.1.relative-jndi-name = jdbc/worklight_adapter
- custom-db.1.driver = com.mysql.jdbc.Driver
- custom-db.1.url = jdbc:mysql://localhost:3306/worklight_adapter
- custom-db.1.username = root
- custom-db.1.password = root
所有带有 custom 属性的参数均为Worklight 保留的参数,适配器部署到服务器后,系统会读取 XML 文件中的 dataSourceJNDIName 属性,然后根据 JNDI 配置的信息来建立数据库的连接。
清单 2. SQL 适配器配置
- <connectivity>
- <connectionPolicy xsi:type="sql:SQLConnectionPolicy">
- <dataSourceJNDIName>${adapter-jndi-name}</dataSourceJNDIName>
- </connectionPolicy>
- <loadConstraints maxConcurrentConnectionsPerNode="5" />
- </connectivity>
- <procedure name="getAllCountries"/>
- <procedure name="getOneCountryID"/>
清单 3. SQL 适配器方法实现
- var procedure1Statement =
- WL.Server.createSQLStatement("select * from country where name = ?;");
- function getAllCountries() {
- return WL.Server.invokeSQLStoredProcedure({
- procedure : "getAllCountries",
- parameters : []
- });
- }
- function getOneCountryID(name) {
- var result = WL.Server.invokeSQLStatement({
- preparedStatement : procedure1Statement,
- parameters : [name]
- });
- var id = result.resultSet[0].index1;
- return getLeagueInfo(id);
- }
在 JS 文件中,函数通过调用 WL.Server.invokeSQLStoredProcedure 方法执行后台数据库提供的存储过程,调用 WL.Server.invokeSQLStatement 方法实现了对 SQL 语句的执行,方法需要的参数和函数的具体说明,用户可以在Worklight 提供的开发者手册上了解它的详细使用过程。
HTTP 适配器
建立 HTTP 适配器的过程类似 SQL 适配器:
- 点击 File-> New -> Other -> Worklight -> Worklight Adapter
- 输入应用名称:HTTPAdapter,选择 HelloAdapterProj 作为工程,在适配器类型中选择 HTTP Adapter,点击确认,完成 HTTP 适配器创建
HTTP 适配器的内容比 SQL 适配器略微复杂一些,除了上面介绍的 XML 和 JS 文件之外,还包括一个 XSL 文件。XSL 文件将根据规则对获取的内容进行转化,去除不需要的数据,改变数据的变量名,这样可以对数据进行先期的编辑。
清单 4. HTTP 适配器配置
- <connectivity>
- <connectionPolicy xsi:type="http:HTTPConnectionPolicyType">
- <protocol>http</protocol>
- <domain>localhost</domain>
- <port>8081</port>
- </connectionPolicy>
- <loadConstraints maxConcurrentConnectionsPerNode="2" />
- </connectivity>
- <procedure name="getLeauge"/>
清单 5. HTTP 适配器方法实现
- function getLeauge(id) {
- var input = {
- method : 'get',
- returnedContentType : 'xml',
- path : "axis2/services/LeagueService/getLeague",
- parameters : {
- 'index' : id,
- },
- transformation : {
- type : 'xslFile',
- xslFile : 'filtered.xsl'
- }
- };
- return WL.Server.invokeHttp(input);
- }
在 XML 文件中配置 WS 的地址,定义提供的方法;然后在 JS 中调用 WL.Server.invokeHttp 方法完成对后台数据的提取和转化。虽然 JS 方法的参数只有一个,但是参数是 JSON 格式,其中包含着单独的变量,其定义如下:
- method: 确认调用的方法是 get 还是 post
- returnedContentType: HTTP 服务返回的数据格式,这影响到应用解析数据的方式
- returnedContentEncoding: 返回数据格式的编码格式,默认为 utf-8
- path: 指定 WS 的路径和方法
- headers: HTTP 请求的头
- cookies: HTTP 请求的 cookie 信息
- body: 在方法是 post 时编写相应的内容
- transformation: 转化数据的参数,包括 XSL 文件的位置和转化的格式
数据的转化
在 第 1 节 中描述了适配器和前台的 app 以及后台系统间数据交互的过程,在第三步中,如果开发者定义了相应的 XSL 文件,那么转化成 JSON 格式的过程就要分为两步:
- 服务器首先将后台的数据转化为 XML 格式
- 通过 XSL 文件转化后,再将数据转化为 JSON 格式
清单 6. XSL 文件转化
- <xsl:template match="/">
- {
- Items: [
- <xsl:for-each select="//*[local-name() = 'return']">
- {
- ranking: '<xsl:value-of select="*[local-name() = 'ranking']"/>',
- name: '<xsl:value-of select="*[local-name() = 'name']"/>',
- round: '<xsl:value-of select="*[local-name() = 'round']"/>',
- win: '<xsl:value-of select="*[local-name() = 'win']"/>',
- draw: '<xsl:value-of select="*[local-name() = 'draw']"/>',
- lose: '<xsl:value-of select="*[local-name() = 'lose']"/>'
- },
- </xsl:for-each>
- ]
- }
- </xsl:template>
在示例中,由于 WS 是根据 Axis2 框架编写的,所以返回的 XML 文件中的标签会有前缀,为了实现只对其中部分数据的抽取,需要使用一定的 XSL 函数进行匹配(在示例中使用了 local-name 函数),具体的 XSL 函数说明参见 XSL 文档。
适配器的测试
在开发平台上,还提供了适配器的测试方法,为了测试适配器,需要按照如下方法进行:
- 右击适配器 -> Run As -> Deploy Worklight Adapter
- 在部署成功后,右击适配器 -> Run As -> Invoke Worklight Procedure
系统会显示如下的测试环境,根据选取的方法和填入的参数,完成对适配器的测试。
图 2. 适配器测试界面
从返回的数据可以看到,如果测试成功,那么系统将返回完整的结果集(resultSet)并且将 isSuccessful 标志位置为 true;如果失败,在 errors 信息位中,有详细的信息,并且 isSuccessful 标志位置为 false。
在示例中,在 WS 没有启动的情况下,测试系统会得到服务器不可连接的错误;而当设置的 XSL 规则有问题时,则会提示描述解析 XML 文件发生错误。
图 3. 适配器测试错误(连接)
图 4. 适配器测试错误(解析)
从上面的描述可以看出,适配器的测试是易于实现的,并且在平台的支持下,提供了详细的调试信息,但是单纯的适配器只能返回数据,为了展示返回的数据,必须和客户端的应用集成,所以接下来的示例将描述应用和适配器集成的方法。
适配器的集成
为了访问部署在服务器上的适配器,应用可以通过Worklight 提供的 JS 函数来访问也可以通过Worklight 支持的本地语言来访问。在 JS 函数情况下方法的参数分为两个部分:
- invocationData: 调用适配器需要的参数
- adapter 表示适配器的名称
- procedure 表示适配器中的函数名称
- parameters 表示传递给适配器的参数
- 回调函数 : 当调用适配器成功或者失败时,需要提供相应的回调函数。onSuccess 函数在调用适配器成功时使用,onFailure 函数在调用适配器失败时使用
清单 7. 客户端对于适配器调用
- function wlCommonInit() {
- busyIndicator = new WL.BusyIndicator("AppBody");
- $('nationList').observe('change', nationSelectionChange);
- getNationList();
- }
- function getNationList() {
- busyIndicator.show();
- var invocationData = {
- adapter : 'MySQLAdapter',
- procedure : 'getAllCountries',
- parameters : []
- };
- WL.Client.invokeProcedure(invocationData, {
- onSuccess : getNationListSuccess,
- onFailure : getNationListFailure
- });
- }
当应用启动时,系统调用 wlCommonInit 函数,通过对 SQL 适配器的访问,调用存储过程,当用户从选择项中,确认一个数据后,调用 SQL 语句获取结果。为了程序流程的简洁,应用只和 SQL 适配器交互,但是当需要调用 HTTP 适配器时,在 SQL 适配器中定义了和 HTTP 适配器交互的代码。
上述流程带来了好处是:因为 HTTP 适配器和 SQL 适配器均部署在服务器端,所以在访问 HTTP 适配器时,不涉及客户端应用和 HTTP 适配器的交互,减少访问的流量;同时暴露的接口也只有 SQL 适配器,方便了管理;更为重要的是,适配器可以利 cache 功能,储存相应的数据,减少系统的负载。
通过本地程序访问适配器时,根据语言的不同,实现的方式也不同,在 Java 中需要继承 WLResponseListener 接口,在 Objective-C 中需要继承 WLDelegate 协议,详细的过程可以在开发相应的程序时查看Worklight 的开发文档。
结束语
适配器作为Worklight 平台的一个关键模式,不但方便了开发者将新的应用和原有的系统集成,还为移动应用的开发提供了统一的数据接口。基于这个模式,程序员可以将关键的流程处理放在服务器端,对于客户端只暴露接口,提供输入和输出的定义,这符合编程模式中服务优先的原则。
适配器还提供了两种很重要的功能:
1. 适配器保护功能,在安全配置上,对重要的适配器方法进行保护,这样访问相应的方法就需要进行用户验证;
2. 适配器验证功能,在安全验证上,原有的系统可能有各种验证方式,使用适配器可以在服务器端编程,用于和原系统的验证模式结合,这样就可以适应各种不同的验 证方式。