级别: 中级 Hunter Medney, 高级认证软件专家, IBM
2007 年 6 月 18 日 对于构建用于桌面和设备的解决方案来说,IBM Lotus Expeditor 是一个强大的平台。本文将介绍三种用于脱机应用程序的架构模式以及用于同步客户机和服务器之间数据的方法。为了演示脱机应用程序,本文给出了一个示例并进行了解释。
IBM Lotus Expeditor 提供了一套全面的本地 API 和服务,用于在桌面上构建企业业务应用程序。其中包括本地关系数据库、Web 服务堆栈、可靠安全的消息传递、加密的凭证存储、带调度程序的同步框架(用于定期同步客户机数据和服务器数据)以及其它服务。
对于创建用于桌面和设备的应用程序来说,Lotus Expeditor 是一个令人兴奋的新的中间件平台。它提供了一个综合性环境,不同类型的应用程序和不同资源能够在其中无缝运行。这些应用程序能够在同一个页面上同时显示,能够互相扩展,而且能够交换数据。可以将 Lotus Expeditor 看作是客户端门户,不同提供方的组件可以在这里进行本地聚集。
软件堆栈
如图 1 所示,Lotus Expeditor 是在 Java 和 Eclipse 平台上构建的分层软件堆栈。IBM 在 Java 和 Eclipse 平台之上添加了很多用于实际业务应用程序的增值服务。
图 1. Lotus Expeditor 桌面软件堆栈
默认的 Java 运行时环境(Java Runtime Environment,JRE)是 jclDesktop,它是 IBM 优化的 J2SE 1.4 子集,提供更小的内存占用和更快的类载入速度。可以使用完整的 IBM J2SE 1.5 JRE 随意替换桌面运行时。请参考本文 参考资料 一节中 Lotus Expeditor InfoCenter 的 Device Runtime Environment 部分。
Eclipse 3.2 提供了核心运行时、表示框架、扩展框架、帮助子系统和其他基础服务。Core to Eclipse 3.2 是 OSGi 框架,它是基于 Java 的框架,用于在单个 Java 虚拟机(Java Virtual Machine,JVM)中运行多个应用程序。对于 OSGi 来说,每个应用程序都是一个包。OSGi 将定义包的打包方式、如何定义包的依赖关系、将哪些包向其他包以及其他包元数据公开。可以将 Lotus Expeditor 看作是瘦 OSGi 运行时上的包集合。
在 Eclipse 顶部,Lotus Expeditor 交付了一个增值且基于标准的全面的服务集合,能够在最大化重用现有服务器端组件(例如 J2EE Web 应用程序)的同时构建企业业务应用程序。
上述增值服务包括:
- 本地关系数据库(IBM DB2 Everyplace 和 IBM Cloudscape)
- 关系数据库同步
- 同步框架
- 使用 IBM WebSphere MQ Everyplace 进行消息传递
- Web 服务消费者堆栈
- Web 服务提供者堆栈
- 本地 J2EE Web 容器
- 本地 JSR-168 portlet 容器
- 定制个性化
- 具有安全凭证存储的帐号框架
- 联机/脱机模式
- 网络感知
- 增强的日志记录
- 样式化的 UI 部件
- 使用 Property Broker 进行包之间的通信
- 服务器托管的应用程序部署
- 服务器托管的设置
- Embedded Transaction Container(EJB 子集)
- JNDI 服务
然后在该堆栈之上构建应用程序,而且应用程序可以使用 Java、Eclipse 以及 Lotus Expeditor API 和服务。
客户机/服务器数据访问架构
业务应用程序将处理数据,包括客户、订单、管理、书籍、保险报价、库存量等等。通常该数据的主版本存储在关系数据库的中央服务器上。被设计为在桌面和设备(客户机)上运行的业务应用程序能够以各种方式访问并修改业务数据,反过来被修改的数据必须与主存储库同步。本文将研究客户机/服务器应用程序的数据访问架构。
可以将客户机/服务器数据访问架构分为始终脱机(always offline)、始终联机(always online)或混合(mixed)架构。每种数据访问模式规定了不同的用于客户端用户界面的持久存储设计。
每种模式都假定一个服务器数据存储和一个本地数据存储。服务器数据存储是企业关系数据库,如 IBM DB2、Oracle 或 Microsoft SQL Server。本地数据存储是 Lotus Expeditor Client 中所提供的本地关系数据库。Lotus Expeditor 提供了 IBM Cloudscape 和 IBM DB2 Everyplace 作为本地数据库选项。
IBM Lotus Notes/Domino 也是客户机/服务器应用程序。因为新的 Lotus Notes V8 客户机是在 Lotus Expeditor 平台之上构建的,并且很多读者都熟悉 Lotus Notes,所以每种模式也是以常见的 Lotus Notes 概念来表达的。
始终脱机
图 2 展示了始终脱机模式。在该模式下,无论客户机实际上能否连接到服务器数据存储,客户机始终与本地数据库的数据进行交互。如果可能,本地数据库将按常规的时间表或用户的要求与服务器数据库进行同步。
图 2. 始终脱机的客户机/服务器数据访问架构
在 Lotus Notes 中,这种模式就相当于用户始终访问本地数据库副本。用户定期使用服务器数据库来复制本地数据库。用户始终没有打开使用了服务器副本的数据库。
同步不必始终按照时间表发生。当客户机联机时 —— 即客户机已经连接到服务器 —— 在对本地数据库进行更新之后,将立即触发数据库同步。客户机发生更改时,上述操作将有效提供服务器端数据库的即时更新。Lotus Expeditor 提供了网络感知 API 与 Online/Offline UI 控件来确定连接。
始终脱机模式的优点包括:
-
可用性。无论客户机是否连接,客户机应用程序能够始终起作用。
-
快速的性能。访问本地数据库始终比访问远程数据库要快。
-
简少了服务器负载。将客户机/服务器的交互限制为同步行为。
始终联机
图 3 展示了始终联机模式。在该模式下,客户机始终读写服务器端数据存储。通常将远程程序调用(Remote procedure call,RPC)Web 服务或基于 REST 的 API 用于此类客户机/服务器交互。
对于客户机/服务器交互来说,基于 REST 的 API 是除 Web 服务之外的另一种选择。REST 模式假定客户机想要创建、读取、更新和删除(CRUD)远程资源。资源可以是图片、订单之类的任何内容。在 REST 模式中,客户机将处理远程资源并提供 CRUD 操作作为 HTTP 请求方法(GET、PUT、POST、DELETE)。接着服务器通常会返回带有结果的特定于应用程序的 XML 文档。
在 RPC 式的 Web 服务中,通过调用诸如 getPerson() 和 updatePerson() 这样的远程操作,客户机将对远程资源执行 CRUD 操作。请求和响应都被包装在 SOAP 封包中。新型工具使得开发人员能够避开创建和读取 SOAP 信封的工作。
图 3. 始终联机的客户机/服务器数据访问架构
例如,客户机对服务器端应用程序调用 RPC Web 服务方法(例如 createTimesheet() 或 getOrdersByCustomer()),以便与数据存储进行交互。不赞成使用 JDBC 从客户机对服务器进行直接数据库访问,因为这样将创建紧耦合,使应用程序的维护变得非常复杂。
该架构类似于使用 Ajax 的 Web 浏览器体验。在 Ajax 中,UI 和应用程序被下载到浏览器并在其上执行。然后浏览器使用 JavaScript 来读/写应用程序所需的数据,而不必强制用户重新载入页面。这就类似于这样一个 Lotus Expeditor 应用程序,它已经包含 UI 和应用程序逻辑,而且只需要从服务器读取数据或向服务器写入数据。
在 Lotus Notes 中,这种模式等同于始终使用 Lotus Notes 数据库的服务器副本的用户。用户始终没有在本地复制数据库。
该架构有三个主要优点:
- 客户机始终使用当前数据。
- 即时更新服务器数据存储。
- 不需要处理任何同步逻辑和冲突。
混合架构
图 4 展示了混合架构模式。在混合架构中,客户机应用程序始终知道客户机是否联机。如果联机,则客户端应用程序将对服务器端数据存储进行操作。如果脱机,客户端应用程序将对本地数据存储进行操作。当客户机联机时,客户机将定期使服务器与本地数据库所做的更改保持同步。表示层必须知道客户机处于哪一种模式下,并相应地呈现适当的部件和数据。
图 4. 混合的客户机/服务器数据访问架构
若要在联机而不是脱机时启用不同的应用程序行为,Lotus Expeditor 要了解全部的联机/脱机客户机状态。联机/脱机状态可以由用户通过 UI 控件来确定,或者以编程方式设置。客户端应用程序可以向平台询问当前的联机/脱机模式,还可以响应更改模式事件。
Lotus Expeditor 还提供了网络感知 API,使得应用程序能够查询物理的网络连接以及特定远程资源的可用性。请参考本文 参考资料 一节 Lotus Expeditor InfoCenter 清单中有关网络感知部分。
该模式是三种模式中最复杂的模式,可以将它看作是其他两种模式的组合,提供了二者的最佳性能。该模式很适合以下用户:
- 大部分时间以良好带宽连接的用户
- 偶尔脱机的用户
- 希望在脱机时获取数据或进行更新的用户
在 Lotus Notes 中,该模式等同于通常在办公室内使用服务器副本,而出差时使用本地副本的用户。在出差前,用户使用服务器数据库进行复制,从而在断开连接之后可以使用最新更改的数据库。当回到办公室后,他或她将本地数据库更改复制到服务器。这是 Lotus Notes 中的常见使用模式。
该模式的另一种选择是使用始终脱机模式并编写应用程序,当本地数据库发生更改时,将在后台立即进行同步操作。这种体验比较接近混合架构,但实现起来更加简单,因为表示层和应用程序逻辑始终是针对本地数据库的。
该架构有以下优点:
- 在能够实时使用数据时,它将利用连接性优势并获得始终联机架构的全部优点。
- 为应用程序提供脱机体验。
数据同步选项
在始终脱机和混合架构中,本地更改需要传递到服务器,并且客户机需要从服务器接收更新。本节将讨论不同的数据同步选项。
使用 IBM DB2 Everyplace 进行数据同步
Lotus Expeditor Server 包括 IBM DB2 Everyplace Sync Server,使得 Lotus Expeditor Client(桌面和设备)能够同步本地关系数据库和服务器端数据库。Lotus Expeditor Client 包括一个 ISync 客户机,将连接到 DB2 Everyplace Sync Server 并完成同步。DB2 Everyplace Sync Server 和 ISync 提供了一个用于同步关系数据的强大的开箱即用引擎。
DB2 Everyplace 的优点包括:
- 包含在 Lotus Expeditor Server 中。
- 它是可靠的企业数据同步产品。
- 提供行级安全。
- 可以在每个用户的基础上筛选数据。
- 提供完整的冲突处理逻辑。
- 通过数据库镜像,减少了源数据库负载。(设备将对复制的镜像数据库进行同步,而不是直接对源数据库进行同步。)
DB2 Everyplace 同步有两个主要缺点:
- 用户必须安装并管理 DB2 Everyplace Sync Server 环境。
- 对于每个应用程序来说,仅仅进行数据同步可能是不够的。应用程序可能需要事务化方法,其中设备将提交更新多个后端系统(而不是单个数据库)的事务。
使用 RPC Web 服务或 REST API 进行数据同步
使用远程过程调用 Web 服务或基于 REST 的 API 也可以实现数据同步。这些技术提供了更加事务化的模型,其中客户机以 HTTP 请求发送并接收数据更新。然后服务器可以更新后端数据库和来自客户机请求的其他资源。例如,服务器可以提供 RPC Web 服务方法,例如 getTimeSheets()、updateBillCode() 和 deleteOrder(),这些方法可以传递并/或返回 Java 对象。
使用 RPC Web 服务或 REST API 时,本地和远程数据库并没有直接同步。相反,对象或数据将以 HTTP 请求跨网进行传递,客户机和服务器都将根据该数据来更新自己的数据库。客户机和服务器都有一个对象持久层,用来读取数据并向本地数据库写入数据。
基于 REST 的 API 是除 RPC Web 服务之外的另一种选择。与向服务器发送 Web 服务调用不同,客户机(桌面和设备)可以使用 REST 式的 HTTP 请求与服务器进行通信。例如,假定客户机想要检索服务器上的订单列表。在 Web 服务中,RPC 调用 getOrders() 以 SOAP 信封方式发送到服务器,然后订单集合以 SOAP 信封方式返回。在基于 REST 的 API 中,客户机将 GET HTTP 请求发送到 sa URL,例如 http://example.com:1234/api/rest/orders,然后服务器返回带有订单的 XML 文档。
虽然 Web 服务调用比起基于 REST 的 API 调用要复杂的多,但 Web 服务比较成熟并且获益于工具,即使得开发人员有效避开复杂性。在客户端,Lotus Expeditor Toolkit 拥有从 Web 服务的 WSDL(Web Services Definition Language,Web 服务定义语言)生成远程 Web 服务的本地代理的向导。这些本地代理能够调用本地 Java 方法,例如从服务器返回 Order 对象数组的 getOrders() 方法。然后通常将代理所返回的对象映射到本地模型中所使用的对象。大多数服务器端平台可以使用类似工具来获取一个服务类并生成 Web 服务提供者和该服务的 WSDL。
使用 RPC Web 服务或 REST API 来同步客户机和服务器时,必须执行同步逻辑来识别本地更新和远程更新,并相应地发送并接收这些更新。该逻辑可以包含在客户机上或在客户机和服务器间传播。
下面是一个示例处理流程,使用 RPC Web 服务来同步客户机和服务器上的订单。客户机遵循推-拉方法,首先将本地更新推到服务器,然后服务器从包含刚才所推入的更新的服务器拉出更新。在本例中,服务器上的给定记录始终优先于客户机记录,因为服务器的记录通常拥有由后端设置的新数据。
- 用户带着笔记本电脑离开办公室,电脑中有用来输入部分订单的 Lotus Expeditor 应用程序。离开办公室后,用户与公司内部网没有任何连接。
- 断开连接后,用户将新订单输入 Lotus Expeditor 部分订单输入应用程序。
- 订单输入应用程序在本地数据库中将该订单标记为 Pending Create,以表明需要在服务器上创建该订单。
- 然后用户返回办公室。现在 Lotus Expeditor 连接到订单输入服务器。
- 在 Lotus Expeditor 同步页面上,用户单击 Order Entry 项中的 “Synchronize the selected application now” 来初始化同步。Lotus Expeditor 给出一个同步框架,其中可以从中央同步页面(与 Lotus Notes 复制器页面极为相似)管理应用程序同步操作。用户可以手动开始同步,定义同步时间表,并/或定义(像客户机启动那样)触发同步的平台事件。
- 订单输入应用程序将本地数据库中所有待解决的更新都推到服务器。对于被标记为 Pending Create 的每一个本地订单,订单输入应用程序都将在订单输入服务器 Web 服务上调用 createOrder() 方法,传递从本地数据库构造的 Order 对象。
- createOrder() 调用将成功完成(不会返回任何异常)。
- 订单输入应用程序将从已成功完成的本地订单中删除 Pending Create 标记。
- 然后,订单输入应用程序拉出自最后一次成功同步之后的所有更新。这些更新包括其他用户提交的订单以及该客户机刚刚推出的订单。客户机从服务器请求自最后一次成功同步之后的所有已修改或创建的订单。客户机将保存最后一次成功同步的时间戳,并在请求已更新订单时使用它。
- 订单输入应用程序将从服务器接收到的更新保存到本地数据库中。当订单输入应用程序从服务器接收的订单在客户机上也存在时,应用程序将使用服务器订单来取代本地订单。服务器上的订单始终处于优先地位。
这些因素包括以下优点:
- RPC Web 服务和 REST API 能够进一步解耦客户机和服务器。只要 Web 服务或 REST API 约定保持不变,客户机和服务器可自由更改数据存储实现。
- RPC Web 服务和 REST API 不依赖于 DB2 Everyplace Sync Server。
- RPC Web 服务和 REST API 是后端不可知的,这意味着 Lotus Expeditor 客户机可以直接同步来自任何供应商的应用服务器。
- RPC Web 服务和 REST API 启用了事务式模型,其中客户机 Web 服务调用或 REST API 请求并没有仅局限于远程资源的 CRUD(创建、读取、更新、删除)操作。客户机 Web 服务调用或 REST API 请求还可以触发其他后端的更新以及其他工作流。
一个缺点是用户必须编写实际的同步逻辑和任何冲突解决逻辑。本文给出的 Contacts 示例展示了一个使用 Web 服务的数据同步例子。
使用 IBM WebSphere MQ Everyplace 进行数据同步
在客户端更新能够被发送到服务器之前,可以使用 IBM WebSphere MQ Everyplace 来存储这些更新。WebSphere MQ Everyplace 在两个设备之间提供了点到点的确定的消息传递。WebSphere MQ Everyplace 的特性包括:
- 异步或同步消息传递
- 支持广泛的具有小型可定制内存使用的设备
- 验证、加密、认可和压缩
- 自动通道管理
- 全面的内置安全特性
- 对象消息传递(数据和函数)
- 可靠的一次性交付
- 与 IBM WebSphere MQ 相结合
如图 5 所示,WebSphere MQ Everyplace 可以为向服务器发送客户机数据提供除 Web 服务或 REST API 之外的另一种选择。使用 Web 服务或 REST 时,要实现将待解决更新记录到服务器的逻辑。而使用 WebSphere MQ Everyplace 时,将更新(例如新订单)放入 WebSphere MQ Everyplace 消息中,然后将该消息放入远程队列中。WebSphere MQ Everyplace 将从那里获取消息,从而将新订单消息传递到服务器。可以将服务器设置为在接收和处理客户机消息时发送回复消息。接收到来自服务器的回复消息后,客户机认为此更新已完成。
图 5. 使用 WebSphere MQ Everyplace 的数据同步
IBM WebSphere MQ Everyplace 提供了向服务器发送更新的另一种选择。使用前面所讨论的方法(IBM DB2 Everyplace Sync Server/ISync、Web 服务或 REST API),客户机可以从服务器接收更新。
下列所述是一个向服务器发送订单的端到端示例:
- 用户带着笔记本电脑离开办公室,电脑中有用来输入部分订单的 Lotus Expeditor 应用程序。离开办公室后,用户与公司内部网没有任何连接。
- 断开连接后,用户将新订单输入 Lotus Expeditor 部分订单输入应用程序。
- 订单输入应用程序将订单放入 WebSphere MQ Everyplace 消息。
- 订单输入应用程序将 MQ Everyplace 消息写入本地队列管理器中所定义的远程队列。
- WebSphere MQ Everyplace 尝试将包含订单的消息传递到远程队列管理器上的 WebSphere MQ Everyplace 队列中。
- WebSphere MQ Everyplace 未能传递消息,因为用户处于断开连接状态。
- 用户返回办公室并连接到网络。
- WebSphere MQ Everyplace 将订单消息成功地传递到服务器。
- 服务器向笔记本中的本地队列管理器发送回复消息。
- WebSphere MQ Everyplace 将通知订单输入应用程序。
- 订单输入应用程序将订单状态更新为 Received。
使用 WebSphere MQ Everyplace 的数据同步利用 WebSphere MQ Everyplace 基础设施,实现了从客户机到服务器的安全可靠的消息传递。但是与这个优点相对应的是两个缺点。用户必须安装并管理 WebSphere MQ Everyplace 环境。另外,通过向 Web services/REST 调用添加某些重试和异常处理逻辑,也可以实现类似的 WebSphere MQ Everyplace 性能。
Contacts 示例:使用 RPC Web 服务进行同步的联机/脱机应用程序
为了演示使用中的脱机应用程序,这里给出了一个同步的联系人示例。该应用程序包含一个简单的联系人列表,它使用前面所讨论的混合模式来同步客户机和服务器。
Lotus Expeditor Toolkit 包含用于 WebSphere MQ Everyplace 和 DB2 Everyplace Sync Server 的示例,因此该示例使用 RPC Web 服务进行数据同步。Web 服务也允许该示例运行,而不必安装 DB2 Everyplace Sync Server 或 WebSphere MQ Everyplace 产品。
Contacts Sample 应用程序展示了当客户机处于联机模式时,将显示服务器上的联系人列表。而客户机处于脱机模式时,从本地 DB2 Everyplace 数据库显示联系人。
因为 Lotus Expeditor 包含自己的 Web 容器和 Web 服务提供者堆栈,所以本示例的服务器部分与客户机都位于 Lotus Expeditor 上。contact 服务器包提供了 contact 客户机包所使用的 Web 服务。客户机和服务器仅通过这些 Web 服务调用来进行交互。
Contacts Sample 包
表 1 展示了 Contacts Sample 应用程序中所使用的包。
表 1. Contacts Sample 包
| 包 | 说明 |
|---|
| com.ibm.dw.contacts.client | 主包。该包是联机/脱机客户端应用程序,而且包含所有 UI 和同步逻辑。
该包使用 Web 服务与服务器包进行通讯,而且使用 DB2 Everyplace 作为本地关系数据库来存储本地联系人。 |
|---|
| com.ibm.dw.contacts.server | 服务器包。通过提供 Web 服务,该包充当远程服务器。Web 服务位于 Lotus Expeditor Web 容器和 Mobile Web Services 堆栈中。
在 Lotus Expeditor 启动时,该包没有任何 UI 和自启动。使用 IBM Cloudscape 来存储服务器端联系人。 |
|---|
| com.ibm.dw.contacts.featurew | 使项目具备包含两个包的特性。这需要创建安装工件。 |
|---|
| com.ibm.dw.contacts.updatesite | 为这种特性更新站点项目。这需要创建安装工件。 |
|---|
下面来看一下创建本地联系人并与服务器同步的端到端过程。
步骤 1:创建新的本地联系人
首先,用户在脱机模式下打开 Contacts Sample。在 Lotus Expeditor 中,联机/脱机模式是由右下区域中的开关控制的。而在 Lotus Notes V8(在 Lotus Expeditor 上构建的)中,联机/脱机模式是由当前的位置设置控制的。
图 6 表明 Contacts Sample 正在使用本地数据库(如标题所示),并且添加了名为 NEW USER 的新联系人。
图 6. 向本地数据库添加了新联系人
Contacts Sample 在数据访问中使用了多层模式:表示 - 服务 - 数据访问对象 - 数据库。在这一步骤中,Add 按钮将收集数据,然后调用 LocalContactService 来添加新联系人。
LocalContactService 将调用 LocalContactDAO 来保存 Contact 对象。利用 Contact 对象中的字段,LocalContactDAO 使用 JDBC 调用在本地 DB2 Everyplace 数据库中保存 Contact 对象。
清单 1 中的代码片段展示了 LocalContactService 中的 createContact() 方法。
清单 1. createContent() 方法
public void createContact(Contact contact) throws Exception {
contact.syncStatus = Contact.SYNC_PENDING_CREATE;
insertContact(contact);
}
|
请注意 syncStatus 字段。该字段将标记出当客户机进行同步时需要在服务器上创建的联系人。insertContact() 方法将调用 LocalContactDAO 中的 createContact()。
清单 2 展示了 LocalContactDAO 中的 createContact() 方法,该方法将保存联系人。
清单 2. createContact() 方法
public void createContact(Contact contact) throws Exception {
logger.entering(getClass().getName(), "createContact", contact);
Connection conn = getConnection();
try {
PreparedStatement pstmt = conn.prepareStatement(SQL_INSERT_CONTACT);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
pstmt.setString(1, contact.guid);
pstmt.setString(2, contact.fname);
pstmt.setString(3, contact.lname);
pstmt.setString(4, contact.phone);
pstmt.setInt(5, contact.syncStatus);
pstmt.execute();
pstmt.close();
} finally {
conn.close();
}
logger.exiting(getClass().getName(), "createContact");
}
|
步骤 2:同步
下一步是使用图 7 所示的 Lotus Expeditor 同步页面,将本地更改同步到服务器。请注意仅当 Lotus Expeditor 处于联机模式时才能完成同步操作。
图 7. Lotus Expeditor 同步页面和 Contacts Sample 项
将 Lotus Expeditor 同步页面上的条目称作同步单元。调用 Contacts Sample 同步单元后,最终将调用 ContactSyncService.sync(),如清单 3 所示。
清单 3. 调用 Contacts Sample 同步单元
public void sync() throws Exception {
logger.info("Sync: start");
pushLocalUpdates();
pullRemoteUpdates();
logger.info("Sync: end");
}
|
ContactSyncService.sync() 使用推 - 拉方法进行同步。首先,将所有被标记为 create-pending、update-pending 或 delete-pending 的本地联系人发送到服务器。
清单 4 中的代码展示了 pushLocalUpdates() 实现。clearSyncFlag() 方法只是从每个联系人删除同步标记,因而不会再次进行处理。
清单 4. pushLocalUpdates() 实现
private void pushLocalUpdates() throws Exception {
logger.info("Sync: pushing local updates to server");
Contact[] pendingContacts = localContactService.lookupPendingContacts();
for (int i=0; i<pendingContacts.length; i++) {
if (pendingContacts[i].syncStatus == Contact.SYNC_PENDING_CREATE) {
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
remoteContactService.createContact(pendingContacts[i]);
clearSyncFlag(pendingContacts[i]);
logger.info("Sync: created new contact on server: " + pendingContacts[i]);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
} else if (pendingContacts[i].syncStatus == Contact.SYNC_PENDING_UPDATE) {
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
remoteContactService.updateContact(pendingContacts[i]);
clearSyncFlag(pendingContacts[i]);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
logger.info("Sync: updated contact on server: " + pendingContacts[i]);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
} else if (pendingContacts[i].syncStatus == Contact.SYNC_PENDING_DELETE) {
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
remoteContactService.deleteContact(pendingContacts[i]);
localContactService.removeContact(pendingContacts[i]);
logger.info("Sync: deleted contact on server: " + pendingContacts[i]);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
}
}
}
|
一旦将所有本地更改推到服务器之后,sync() 方法将从服务器拉出所有更新。这些更新包括刚刚推出的更改。在本应用程序中,服务器记录始终优先于客户机记录。例如,服务器记录可以包含由服务器设置的其他字段。
清单 5 中的代码展示了 pullRemoteUpdates() 实现。该方法将请求自最后一次成功同步之后的所有服务器更新,然后通过创建、取代或删除本地联系人来更新本地数据库。成功处理每一个服务器更新之后,客户机将记录更新的时间戳。最后一个记录的时间戳将用于下一次同步,以便找到新更新。
清单 5. pullRemoteUpdates() 实现
private void pullRemoteUpdates() throws Exception {
logger.info("Sync: pulling remote updates");
long timestamp = store.getLong(IConstants.PREF_LAST_SYNC_TIME);
Contact[] updatedContacts = remoteContactService.lookupUpdatedContacts(timestamp);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
// sort contact list so they are processed in-order
List contactList = Arrays.asList(updatedContacts);
sortContacts(contactList);
for (Iterator it = contactList.iterator(); it.hasNext();) {
Contact remoteContact = (Contact)it.next();
Contact localContact = localContactService.lookupContact(remoteContact.guid);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
if (localContact == null && !remoteContact.serverDeleted) {
// new contact
localContactService.insertContact(remoteContact);
updateTimeStamp(remoteContact);
logger.info("Sync: created new contact on local: " + remoteContact);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
} else if (localContact != null && remoteContact.serverDeleted){
// delete local contact
localContactService.removeContact(remoteContact);
updateTimeStamp(remoteContact);
logger.info("Sync: deleted contact on local: " + remoteContact);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
} else if (localContact != null && !remoteContact.serverDeleted) {
// replace local contact
localContactService.replaceContact(remoteContact);
updateTimeStamp(remoteContact);
logger.info("Sync: replaced contact on local: " + remoteContact);
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
}
}
}
|
步骤 3:确认服务器上的新联系人
客户机处于联机模式下时,切换回 Contact Sample 应用程序来确认已显示新的本地联系人,如图 8 所示。
图 8. 已将本地联系人同步到服务器,而且当客户机访问联机存储时,将显示新联系人
当客户机处于联机模式时,将使用 Web 服务调用从服务器填充联系人表。现在新联系人将出现在该列表中,因为它目前存在于服务器的数据库中。
运行示例
按照以下步骤在 Lotus Expeditor 中安装示例。
- 从本文的 下载 一节中下载 contacts-sample 压缩文件。
- 在 Lotus Expeditor 中,选择 File – Application – Install。
- 在 Install/Update 对话框中,选择 “Search for new features to install”。
- 在 Install 对话框中,单击 Add Zip/Jar Location 并转到您在第一步中下载的 contacts-sample.zip 文件。选中该文件后,文件将作为安装位置出现。
- 在 Install 对话框中单击 Finish。将出现 Updates 对话框,展示 contacts-sample.zip 的可用特性。选择要安装的特性,如图 9 所示。
图 9. 作为安装位置的 Contacts Sample
- 接受许可协议并重新启动 Lotus Expeditor,从而完成安装向导。完成重新启动后,示例将出现在 Open 菜单中,如图 10 所示。打开应用程序如图 11 所示。
图 10. 现在 Contacts Sample 出现在 Open 菜单中
图 11. Contacts Sample 应用程序。应用程序初始化期间,将在服务器上自动创建 “Default User”。
结束语
Lotus Expeditor 平台的主要优势之一是能够构建可以脱机工作的应用程序。本文介绍了用来设计联机/脱机应用程序的三个通用架构模式。这些模式确定了应用程序处理数据的方式。
第一种模式是始终脱机模式。在该模式下,用户始终与本地数据存储中的数据进行交互。应用程序将定期使用服务器上的数据来刷新本地存储,并将用户所做的更改传递到服务器。因为表示层始终使用本地数据存储,所以该模式简化了联机/脱机开发。
第二种模式是始终联机模式。在该模式下,用户始终与远程数据存储中的数据进行交互。在本地提供业务逻辑和 UI,并且应用程序向服务器请求数据。该模式类似于使用 Ajax 的基于浏览器的应用程序。
第三种模式是混合模式。在该模式下,当客户机联机时,用户与远程数据存储进行交互;而当客户机脱机时,用户与本地数据存储进行交互。该模式允许用户在连接可用时使用服务器连接,而在连接不可用时提供脱机体验。不足之处是该模式实现起来是最复杂的。
为了演示混合模式,本文给出了一个示例。Contacts Sample 应用程序使得用户能够维护一组联系人。联机时,用户使用服务器数据存储中的联系人;脱机时,用户使用本地存储中的联系人。用户可以在 Lotus Expeditor 同步页面上同步服务器数据存储和本地数据存储。
在始终脱机和混合模式下,需要在客户机和服务器之间同步数据。本文还回顾了进行数据同步的三种方法。这些方法包括使用 IBM DB2 Everyplace Sync Server、使用 Web 服务或 REST API 以及使用 IBM WebSphere MQ Everyplace。
下载 | 名字 | 大小 | 下载方法 |
|---|
| contacts-sample-src.zip | 180KB | HTTP | | contacts-sample.zip | 63KB | HTTP |
参考资料 学习
讨论
关于作者  | |  | Hunter Medney 致力于最大化客户在 IBM Lotus 技术上的投资回报。他是 IBM Software Services for Lotus 的一名高级认证软件专家,专攻 IBM Workplace 产品系列。他在 IBM Lotus Notes/Domino 方面具有广泛的背景,并已迁移到 J2EE 和即将取而代之的 IBM Workplace Client Technology。通过创建公共示例、构建开发人员内部社区、出席会议、撰写文章和为 developerWorks 撰稿,他为这一领域的应用程序开发做了很多基础工作。 |
对本文的评价
|