基于 Lotus Expeditor 6.1 的复合应用开发

2007 年 7 月 27 日

面对市场激烈的竞争,企业需要进行快速响应,以满足市场的需求,复合应用是提高企业业务的灵活性的一个有效的解决方案, 使企业可以充分利用和扩展现在的资源,节约成本, 最大限度地复用组件。 本文主要讨论复合应用的概念以及如何开发基于 Lotus Expeditor 的复合应用,通过一个 Eclipse 实例,展示了具有松散耦合的复合应用的开发过程。

引言

复合应用是面向服务架构(SOA)中的关键要素,它能为企业提供业务灵活性的同时,能够更容易创建新服务或者将现有的IT资产转化为可重用的服务,这些服务可以快速适应不断变化的业务需求。复合应用是由多个相互通讯相互操作的组件组成,而这些组件是松散耦合的,并且在其它复合应用中重复使用。一个组件中的数据发生了变化,可以通过消息广播通知给其它组件,与之线接(Wire)的组件收到消息后进行相应的处理以反映到组件中数据的变化。

本文主要讨论复合应用的概念以及如何开发基于 Expeditor 的复合应用,通过一个 Eclipse 实例,展示了具有松散耦合的复合应用的开发过程。





回页首


Lotus Expeditor 客户端平台

Lotus Expeditor 6.1 是全新的 IBM 智能客户端平台软件,它提供的客户端可以通过服务器进行管理,可以远程部署和维护,它的客户端是多种应用集成的复合界面,支持离线应用等。

复合桌面

可以将部署后的不同的应用组件集成在一个可视的窗体里,而且可以集成后台的业务流程应用。用户不必在不同应用之间进行来回切换,还可以集成工作流在一个复合应用里,复合应用的组件可以是 Swing、文字终端、Visual Basic、Active X、本地或 Web 的 Applet 如 AJAX、PDF、JSP 和 Forms。Lotus Expeditor 还提供了这些组件之间互相通讯,相互操作的能力。不仅可以使得一个应用作为复合桌面的一个组件,而且通过其它新的应用增强现有应用的功能。利用 Lotus Expeditor,Portal 可以是复合应用的一个组成部分,现有的应用可以经过简单的封装,作为复合桌面中的一个组件,最终用户在不影响其操作习惯的基础上,使用集成的复合桌面。这样可以将 Lotus Expeditor 贯穿所有的业务,从而降低最终用户的重新培训的成本,提高生产效率。

集成

Lotus Expeditor 可以将不同数据源的信息集中展现在用户眼前,这些信息可以通过 Portal 应用、Workplace Form 应用、关系数据、Web 服务等形式展现。Lotus Expeditor 为 Portal 提供了一个复合桌面,将 Portal 应用通过响应快速、界面丰富的桌面应用展现出来,而 Portal 的拓扑结构在客户端维护,通过视图的形式展现 Protlet 以及相互间的通讯,而且 Portal 应用还可用于离线模式,即使网络不连通、不稳定的情况下,仍能高效的工作,这特别适用于移动办公的职员和外勤人员。

Lotus Expeditor 可以使 Workplace Forms 应用变成离线应用,表单以及表单数据可存储在桌面客户端,当网络连通后,可以和 Form 服务器进行同步。有些表单,特别是复杂表单,用户可以离线填写,例如在家里填完后,保存在本地,当用户有网络时,再向服务器提交,这可极大的提高办公效率。对于关系数据,Lotus Expeditor 可以使客户不必重写同步算法就可以使后台 ODBC 或 JDBC 数据源,通过 DB2e 的稳健的同步能力,将后台关系数据与客户端数据进行同步。Lotus Expeditor 的客户端服务能力使得从客户端从接受 Web 服务更加容易,从而可以将后端应用如 Siebal 等将 Web 服务接口扩展到可管理客户端环境。另外,Lotus Expeditor 可以利用 WBI 适配器来通过企业服务总线将不同的后端应用扩展到前端。

服务器端的中心化管理

Lotus Expeditor 可以支持从服务器端的远程部署和维护,这样可大大节约部署和维护的成本,应用管理器能自动完成诸如配置、分发、升级软件、初始化下载等任务,还可以控制组件的访问权限,对管理员和设备用户区别对待。另外,为了提高开发效率,Lotus Expeditor 提供了不同编程模型的选择,开发人员可以开发基于 Eclispe 的用户界面,也可以使用 Servlet 容器,这样开发人员不管是写基于 Eclipse 的应用,还是写 Web 应用,都可以在客户端运行。

支持离线

Lotus Expeditor 支持离线存储的应用,当数据或者应用在在线时可以下载或同步,当设备离线时,用户仍然可以处理事务,并将数据保存到本地,当网络连通时,数据可按应用的逻辑同步到服务器,与服务器的通讯可以是同步,也可以是异步。

安全性

在安全性方面,Lotus Expeditor 提供了诸如加密、凭证存储、锁止桌面等功能来保证客户端的安全性, 本地的凭证存储可以使得通过本地即可验证用户,而不需连通网络,这也使得本地客户端的多个应用通过 Lotus Expeditor 客户端实现单点登录。桌面锁止功能可防止用户从客户端安装非授权的软件,这样,本机上只有服务器管理员安装的软件才能安装于最终用户的机器上。除此之外,Lotus Expeditor 支持多项加密技术(如:关系数据的加密)。





回页首


复合应用

Lotus Expeditor 提供了将不同应用进行组装的能力,这些应用虽然具有各自的功能,但通过组装,它们之间可以相互通讯相互操作,进而使得组装后的应用可看作一个单一的应用,从而在每个应用的功能之上增加了组合功能。复合应用有如下的特点:

  • 组件之间可以相互通讯;
  • 组件之间松耦合,可灵活地用于 SOA 架构;
  • 组件或资源可以复用;
  • 不同组件可以组装在一个客户端窗体中以获得更加丰富的用户体验;
  • 统一的界面风格;
  • 事务型组件其信息取自不同的信息源;
  • 支持诸如验证、角色和数据保密等安全性;

组件

构成复合应用的相互之间可以通讯和操作的应用程序,我们称为组件,这些组件可以动态进行组装和拆卸,组装后虽然他们可以进行相互通讯相互操作,但它们之间仍旧是松耦合的,一旦一个或多个组件移除,并不影响剩余组件的功能。

不同的技术创建的视窗可以组装于一个窗体内,这些视窗可看着作为组件的外部展现,通过复合应用,可以将这些视窗集成起来,构成复合应用。可以用于组装 Expeditor 复合应用的组件有如下类型:

  • SWT 组件;
  • AWT 组件;
  • 内嵌浏览器组件;
  • 支持 JSR168 标准的 Portlet 查看器组件;
  • 显示 Servlet 及 JSP 的 Web 容器;
  • 可在本地显示远程 Portlet 的 WSRP 查看器;

Property Broker

构成一个复合应用的组件之间需要进行相互通讯,但由于这些组件可以是由不同的开发商提供,这就需要一个标准规范,用于指导开发人员开发出可以用于复合应用的组件。在 Expeditor 中,通过 Property Broker 提供了不同组件之间进行通讯的能力,组件的开发者可以通过 XML 配置文档发布组件的数据或属性(Property)、消息的动作(Action)以及组件间的线接(Wire),Property Broker 能够动态的检测到已经变化的组件的数据,并查找注册过的与该组件相连接或线接的其它组件中的动作,如果找到相应的动作,Property Broker 触发该动作使之执行,这样就完成了两个松耦合组件间的通讯及操作。

在这些相互协作的组件中,提供数据的组件我们称之为提供者,而接受数据并进行动作的组件我们称为接受者,提供者的数据我们称为输出数据,接受者的接受的数据我们称为输入数据,如图 1。


图 1. 数据提供者组件的输出数据和数据接受者组件的输入数据
图 1. 数据提供者组件的输出数据和数据接受者组件的输入数据

仅仅定义组件的输出数据或输入数据,两个组件还不能进行相互通讯,只有通过 Property Broker 线接(Wire)起来的组件,才能进行相互通讯相互操作。线接的一端是数据提供者组件中的输出数据,另一端是数据接受者的输入数据,两者的数据类型必须一致,如图 2。


图 2. 两个组件的线接(Wire)
图 2. 两个组件的线接(Wire)

无论是组件发布还是注册,Property Broker 是通过一种 XML 文档来完成的,这一文档的格式是借自 Web 服务描述语言 WSDL(Web Services Description Language)格式,通过这个描述语言,可以描述在复合应用中组件的输入输出数据(property)、消息动作(action)以及类型等。





回页首


Eclipse 组件样例

本节我们给出一个由纯粹 Eclipse 组件构成的复合应用,其功能是将当前北京时间换算为世界其它时间,通过此样例,可以详细了解基于 Lotus Expeditor 客户端的复合应用的构建过程。

此样例由两个组件组成,com.ibm.rcp.samples.cityselection 是用户选取世界城市,并显示当前的北京时间(当前系统时间),另外一个组件 com.ibm.rcp.samples.citytime 是显示选中城市的当前当地时间。如图 3:


图 3. 实例界面:世界城市时间与北京时间换算
图 3. 实例界面:世界城市时间与北京时间换算

消息的触发是由 com.ibm.rcp.samples.cityselection 中,用户点击 Send 后,组件 com.ibm.rcp.samples.cityselection 通过 Property Broker 向组件 com.ibm.rcp.samples.citytime 广播所选择的城市名字,组件 com.ibm.rcp.samples.citytime 收到消息事件后,启动一个动作,以计算并显示当前该城市的当地时间。

消息动作及属性的定义

通过 Property Broker,我们可以声明组件的属性(property)、动作(action)以及线接(wire),以便当松散耦合的某个组件的数据发生变化时,与之线接的组件获得变化的数据并触发相应的动作。 Expeditor 的 Property Broker 提供了一套机制,仅仅通过配置即可将消息动作及属性进行定义,并自动注册到 Property Broker。这种做法的好处是,开发人员可以不必了解 Property Broker 的 API,甚至不必需要 Property Broker 的知识。

Expeditor 客户端 Property Broker 允许有不同类型的组件与之协同工作,当前 Expeditor 有三类动作处理程序:SWT_ACTIONPORTLETAWT_ACTION,开发人员可以根据需求实现这三种动作的一种,Broker 通过动作类型将变化了的数据传递给相应的动作处理程序。

如果一个组件没有 UI 界面,可以通过 org.eclipse.core.commands.IHandler 接口来实现自己的动作,这时动作类型为 COMMAND。如果组件依赖于 Eclipse 的界面,如 SWT 视图,可以用 org.eclipse.core.commands.IHandler 接口来实现动作,并且是 SWT_ACTION 类型,也可以继承 org.eclipse.jface.action.Action 来实现动作。用 SWT_ACTION 类型的好处是当组件所在的透视图隐藏时,复合应用 XML 定义的线接(wire)就会失效。

下面我们讨论如何在组件 com.ibm.rcp.samples.cityselection 中创建一个动作。首先将插件依赖于 com.ibm.rcp.propertybrokercom.ibm.rcp.propertybroker.swt, 然后创建一个 com.ibm.rcp.propertybroker.PropertyBrokerDefinitions 的扩展,这一扩展可自动注册动作以及对应的输入输出数据。Lotus Expeditor Toolkit 提供了一个工具为 Property Broker 定义模板,利用这一导航式工具,可定义出扩展、WSDL 文件及动作的 Java 类文件。在 Property Broker 定义的模板中,可选取模板 SWT Handler,如图 4:


图 4. 扩展点的选取
图 4. 扩展点的选取

在模板的导航页面中,可输入动作的包名,动作的处理 Java 类以及类型的命名空间(如图 5),并按 Finish 按钮。


图 5.模板中的数据项
图 5.模板中的数据项

生成器会自动生成类的代码和 WSDL 文件 wsdl/Sample.wsdl。我们需要将自动生成的 WSDL 文件进行重新命名以满足我们的命名规则,我们将文件名命名为 CityName.wsdl,与之关联的扩展点的细节也需相应的改变,如图 6:


图 6. PropertyBrokerDefinition 扩展点中 wsdl 文件的指定
图 6. PropertyBrokerDefinition 扩展点中 wsdl 文件的指定

我们可以用文本编辑器更改 CityName.wsdl 的内容,也可以用 Toolkit 带的 Property Broker 的可视化编辑器进行编辑修改。修改后的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="OrderDetail_Service"
  targetNamespace="http://www.ibm.com/wps/c2a"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:portlet="http://www.ibm.com/wps/c2a"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:tns="http://www.ibm.com/wps/c2a"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<types>
  <xsd:schema targetNamespace="http://www.ibm.com/wps/c2a">
    <xsd:simpleType name="ResultType">
      <xsd:restriction base="xsd:string">
      </xsd:restriction>
    </xsd:simpleType>
  </xsd:schema>
</types>

<message name="CityName">
  <part name="resultFromCitySelection" type="tns:ResultType"/>
</message>

<portType name="CityName_Service">
  <operation name="CityName_Operation">
     <output message="tns:CityName"/>
  </operation>
</portType>

<binding name="CityNameBinding" type="tns:CityName_Service">
  <portlet:binding/>
  <operation name="CityName_Operation">
    
    <portlet:action name="CityNameAction"
      type="standard"
      caption="CityName.GetCityName"
      description="Get city name"
      actionNameParameter="ACTION_NAME"/>
    <output>
      <portlet:param name="CityName_Text" 
    partname="resultFromCitySelection" 
    boundTo="request-attribute" 
    caption="City Name"/>
    </output>
  </operation>
</binding>
</definitions>

在这个组件 com.ibm.rcp.samples.cityselection 中, 其输出参数只有一个: CityName_Text。我们可以用同样的方法,定义组件 com.ibm.rcp.samples.citytime 中的输入输出参数及动作,wsdl 文件的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="OrderDetail_Service"
  targetNamespace="http://www.ibm.com/wps/c2a"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:portlet="http://www.ibm.com/wps/c2a"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:tns="http://www.ibm.com/wps/c2a"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<types>
  <xsd:schema targetNamespace="http://www.ibm.com/wps/c2a">
    <xsd:simpleType name="ResultType">
      <xsd:restriction base="xsd:string">
      </xsd:restriction>
    </xsd:simpleType>
  </xsd:schema>
</types>

<message name="CityName">
  <part name="resultFromCitySelection" type="tns:ResultType"/>
</message>

<portType name="CityName_Service">
  <operation name="setCityName">
     <input message="tns:CityName"/>
  </operation>
</portType>

<binding name="CityNameBinding" type="tns:CityName_Service">
  <portlet:binding/>
  <operation name="setCityName">
    
    <portlet:action name="setCityName"
      type="standard"
      caption="CityName.GetCityName"
      description="Get city name"
      actionNameParameter="ACTION_NAME"
      activeOnStartup="true"/>
    <input>
      <portlet:param name="setCityName" 
      partname="resultFromCitySelection" 
      caption="CityTime"/>
    </input>
  </operation>
</binding>
</definitions>

在组件 com.ibm.rcp.samples.citytime 中,定义了一个动作 setCityName 以及这个动作的输入参数 CityName

消息动作的实现

利用 Expeditor 的 Property Broker,我们可以定义不同级别的动作实现,如果组件所运行的设备没有 SWT 或 AWT 界面,我们可以用 Eclipse 核心命令的消息处理接口(org.eclipes.core.commands.IHandler)来实现动作,其中 IHandler 接口的 execute() 方法必须实现。PropertyChangeEvent 可以作为消息的触发器并且可以通过 ExecutionEvent.getTrigger() 获得。 以下是处理数据改变时事件响应的动作处理代码:

public Object execute(ExecutionEvent event) throws ExecutionException {
  final Object eventTrigger = event.getTrigger();
  if (eventTrigger instanceof PropertyChangeEvent){
    final PropertyChangeEvent pce = (PropertyChangeEvent)eventTrigger;
    final PropertyValue value = pce.getPropertyValue();
    //TODO implement your handler

  }
  return null;
}

我们的样例是基于 Eclipse 的 Expeditor 客户端,有一个视图用以界面显示,这样我们的事件处理代码可以基于 org.eclipse.jface.action.Action,需要实现的接口函数是 runWithEvent, 代码如下:

public class setTimeAction extends Action {

  public void runWithEvent(Event event) {
    if (event instanceof PropertyChangeEvent)
    {
      final PropertyChangeEvent pEvent = (PropertyChangeEvent)event;
      //System.out.println("Event received");
      Thread t = new Thread() { 
        public void run() {
          PropertyValue value = pEvent.getPropertyValue();
          //TODO implement your handler
        } 
      };
      Display display = Display.getDefault();
      display.asyncExec(t);
    }
  }
}

数据的发送

数据变化的发布可以在任何时候,但一般来讲,是用户通过界面的操作来发起,例如,点击一个按钮或选择列表或表中一行元素等。组件 com.ibm.rcp.samples.cityselection 中,是通过点击 Send 按钮来触发的。Property Broker 的 ChangedProperties() 方法告知 Property Broker,开始向其它组件广播,该组件的参数发生了变化。ChangedProperties 的第二个参数是一个字符串,是与线接配置中的 sourceentityid 一致,我们用当前的 ViewID 来赋值,ViewID 可以用如下的语句获得:

viewID = this.getViewSite().getId() + ":" + this.getViewSite().getSecondaryId(); 

下面是数据发送所用的完整代码,其中 m_sendBtn 是 View 的成员变量,代表 Send 那个按钮。

m_sendBtn.addSelectionListener(new SelectionAdapter() {
  public void widgetSelected(SelectionEvent arg0) {
    PropertyBroker pb = PropertyBrokerFactory.getBroker();

    PropertyValue[] values =  new PropertyValue[1];
    m_BeijingTime.setText( "Beijing Time: "+new Date().toLocaleString() );
    try {
      Property prop = pb.getProperty("http://www.ibm.com/wps/c2a","CityName_Text");
      if(prop!=null) {
        values[0] = PropertyFactory.createPropertyValue(prop, m_City.getText());
        pb.changedProperties(values,viewID);
      }
    } catch (PropertyBrokerException e) {
      e.printStackTrace();
    }
  }
});   

组件的线接(Wire)

没有线接的组件是一个个孤立的组件,只有通过线接的组件,组件之间才能进行通讯,以及进行互操作。为了注册一个线接,需要对扩展点 com.ibm.rcp.propertybroker.PropertyBrokerWire 作扩展,同时定义一个线接,如图 7。


图 7. PropertyBrokerWire 扩展点中数据项的指定
图 7. PropertyBrokerWire 扩展点中数据项的指定

每个线接有如下的属性:

属性名称属性说明
Type类型,当前只支持 PROPERTY_TO_ACTION 线接类型
sourcename当前线接的发出者名字
targetname当前线接的接受者的名字
name线接的名字
title线接的题目(与线接不影响)
ownerid拥有者组件的 id,通过 Property broker 的 API,可以进行查询
odinal在 broker 注册表中的排序
sourceentityid发出者的实体 id
sourceparam线接信息中将要发送给动作的参数
targetentityid接受者的实体 id
targetparam线接信息中动作接受者的动作参数
enable当前线接是否可用,注册时自动设为可用
uid 唯一 id

通常,实体 id 是视图的 id,即视图的第一 id 和第二 id 并在中间加冒号。

如下是我们样例中的线接在 plugin.xml 的描述:

  <extension
         id="com.ibm.rcp.portlet.wire"
         name="Portlet Wire"
         point="com.ibm.rcp.propertybroker.PropertyBrokerWire">
         <wire
              sourceentityid="com.ibm.rcp.samples.cityselection.views.CityNameView:null"
              sourcename="CityName_Text"
              targetentityid="com.ibm.rcp.samples.citytime.views.CityTimeView:null"
              targetname="setCityName"
              type="PROPERTY_TO_ACTION"/>
  </extension> 

当接受者组件收到 property 更改事件时,通过事件获得对应的线接对象,利用 SWTHelper 的 locateView 方法根据接受者组件的实体 id,查找到对应的 View,并对 View 进行刷新。动作的完整代码如下:

public class setTimeAction extends Action {

  public void runWithEvent(Event event) {
    if (event instanceof PropertyChangeEvent)
    {
      final PropertyChangeEvent pEvent = (PropertyChangeEvent)event;
      //System.out.println("Event received");
      Thread t = new Thread() { public void run() {
        PropertyValue value = pEvent.getPropertyValue();
        Wire wire = pEvent.getWireDefinition();
  CityTimeView view = (CityTimeView)SWTHelper.locateView(wire.getTargetEntityId());
        //ColorItem color = new ColorItem();
        String result = null;
        result = (String)value.getValue();
        System.out.println("CityName received is "+result);
        view.setCity(result);
      } };
      Display display = Display.getDefault();
      display.asyncExec(t);
    }
  }
}





回页首


总结

Lotus Expeditor 是一个跨系统的客户端平台,在此平台上,可以将不同的应用或组件集成在一个窗体中,而这些组件之间可以相互通讯相互操作,但又是松散耦合的。本文讲述了复合应用的概念,并通过一个 Eclipse 实例,展示了在 Lotus Expeditor 平台上,复合应用的开发过程。开发人员可以根据这一过程,构建出适合自己企业业务需求的复合应用。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值