使您的应用程序调用我的应用程序,第3部分:资源适配器

本系列教程共分三部分,在 第 1 部分 和 第 2 部分 中,您学习了如何开发消息驱动 bean(MDB)和实体 bean,并在 Apache Geronimo 中部署和测试它们;还了解了如何创建一个电子邮件应用程序,并将其部署到 Java Apache Mail Enterprise Server(Apache James)中。在系列教程的最后这一期中,您将学习与 Java 2 Platform,Enterprise Edition(J2EE)ConnecTor Architecture(JCA) 资源适配器有关的一切内容,构建一个连接到 Apache James 服务器的示例适配器,从而将整个应用程序汇总到一起。

开始之前

本系列教程面向希望学习如何使用各种 Java EE 组件 —— 包括 MDB 和 Java 2 Platform,Enterprise Edition (J2EE)ConnecTor Architecture(JCA)资源适配器 —— 构建集成解决方案的 Java™ Platform, Enterprise Edition(Java EE)程序员。

关于本系列

在这个共分三部分的系列教程中,您将构建一个示例应用程序,通过 这种方式了解如何将不同的 Java EE 组件集成在一起,来开发复杂的应用程序。这个示例应用程序示范了 Apache James 中电子邮件的数 据通过 JCA 资源适配器、MDB、EJB 流向 Apache Geronimo 应用服务器。

本系列的 第 1 部分 介绍了如何开发 MDB、实体 bean 和容器管理的持久性(CMP),以及如何在 Apache Geronimo 中部署和测试这些组件。

本系列的 第 2 部分 解释如何创建电子邮 件应用程序(mailet 和 matcher)并将其在 Apache James 电子邮件服务器中进行部署。

第 3 部分将整个应用程序汇总在一起。您将学习为 Apache James 电子邮件服务器开发、部署和测试 JCA 资源适配器,它将通过 MDB 与 James 和 Geronimo 交互。

关于本教程

这个共分三部分的系列教程的最后一期将详细说明不同 J2EE 组件(MDB 和 JCA 适配器)之间的交互。您将了解基于 JCA 的资源适配 器,并构建一个连接到 Apache James 服务器的示例适配器。

先决条件

本教程假设您熟悉基本的 Java、J2EE 和 Java EE 概念,例如 EJB、Java Message Service(JMS)、MDB 和 Unified Modeling Language(UML)图。不要求您预先具备任何 JCA 的知识。

系统要求

为完成本教程的学习,您需要具备以下工具:

Apache Geronimo — Apache 提供的 Java EE 应用服务器

Apache James 2.2 — 基于 Java 的 Simple Mail Transfer Protocol(SMTP)、Post Office Protocol version 3(POP3)和 Network News Transfer Protocol(NNTP)新闻服务器

Apache Derby 数据库 —— 开放源码、轻量级数据库,嵌入在 Geronimo 之中,无需独立安装

Sun Microsystems 提供的 Java 1.4.2

示例源文件

首先下载 part3.zip(参见 下载 部分),其中包括第 3 部分的源代码、适配器、MDB 和 EJB 二进制文件。下面详细列出了 part3.zip 文件的组成部分:

– deploy(po.ear,包含 mailet 和 matcher)

– lib(tester.jar、examples.jar、mail-1.3.1.jar、activation.jar)

– src(资源适配器、MDB、EJB 和测试客户机的 Java 文件)

– runSendEmail.cmd

– runReadEmail.cmd

概述

在这一部分中,我们将探索应用程序集成的演化史,这最终导致了 JCA 的出现。

应用程序集成简史

让我们回溯到 2000 年之前(比如说),当时集成异构的企业和遗留系统(例如 SAP、Siebel 和 Peoplesoft)不仅仅是花费巨大,更 是一种耗时长的特殊过程。这种过程通常称为企业应用程序集成(EAI),有许多软件供应商将其作为专利服务提供。

到了 2000 年年底,J2EE ConnecTor Architecture(缩写为 JCA,不要与 Java Cryptography Architecture 混淆)第一次为 EAI 引 入了标准。JCA 及 J2EE 1.3 的出现标志着集成解决方案开发在 2001 年的巨大飞跃,也决定了专利技术在集成领域中的衰落。

定义

在研究资源适配器之前,让我们先了解一下以下这些定义:

企业信息系统(EIS):组织中进行传统数据处理的后台层。就本系列教程而言,Apache James 服务器是 EIS。

企业应用程序集成(EAI):链接一个组织内不同企业系统(EIS)的过程。

应用程序编程接口(API)契约:一组预定义的 API,用于在不同的 J2EE 组件间通信。

资源适配器归档(RAR):包含一个资源适配器所需的全部库和描述符。

服务提供者接口(SPI):JCA 规范定义的契约。

通用客户机接口(CCI):JCA 规范定义的契约。

什么是资源适配器?

资源适配器是一个 J2EE 组件,例如 EJB、MDB 等。每个 J2EE 组件都遵循某些规范,一个资源适配器必须遵循一种定义良好的规范, 即 J2EE ConnecTor Architecture。资源适配器在集成 J2EE 解决方案中扮演着一个重要的角色:负责与后台 EIS 的全部通信。

资源适配器起着入口的作用,使任何 J2EE 组件可访问后台资源(通常是 EIS)。它独自承担着连接 EIS、调用 EIS 系统上可用服务 等责任。资源适配器之于 EIS 系统,正如实体 bean 之于数据库。

资源适配器可在 J2EE 应用服务器内部署,这称为托管场景(managed scenario)。非托管场景 就是资源适配器不部署在任何服务器 内,而是独立的。本教程中使用的是托管场景,因为您要将资源适配器部署在 Apache Geronimo 中,它是一个 J2EE 应用服务器。

后文将介绍资源适配器的各种组件。首先,您需要了解规范本身。

J2EE 连接器架构规范

JCA 规范定义一组契约,管理一个资源适配器的各个方面,例如连接或事务等。这一部分解释 JCA 1.0 和 1.5 这两个版本中定义的契 约。

JCA 1.0

JCA 1.0 是在 2000 年 11 月发布的第一个规范。它在更广泛的级别定义了两种类型的契约,即系统级和应用程序级契约。

系统级契约 定义了资源适配器和 J2EE 应用服务器之间的通信和握手,而应用程序级契约 定义了客户机应用程序和资源适配器之间的 通信。这些契约对于一个资源适配器的客户机来说是透明的。

系统级契约(也称为 SPI)定义了多种不同的契约,例如:

连接管理:定义一个资源适配器和一个 J2EE 连接管理器(J2EE 应用服务器内的一个组件)之间的通信,以支持连接池。资源适配器 与 EIS 的连接是在部署过程中创建的,并置于连接池,以提高可伸缩性和性能。

事务管理:支持事务管理,定义事务管理器和资源适配器之间的握手。它支持两种类型的事务:本地和 XA。本地事务 名符其实,对于 EIS 及其资源适配器来说是本地的。XA 事务 对于 EIS 来说是外部的,由应用服务器事务管理器托管。XA 事务能够封装对不同资源的多 个调用,例如不同的 EIS 系统、数据库等等。它们支持一种两阶段提交协议。

安全性管理:支持资源适配器和 J2EE 应用服务器间的不同安全性机制,例如身份验证和授权。

应用程序级契约(也称为 CCI)为应用程序组件定义一组客户机 API,例如 EJB 和应用程序客户机,以便与资源适配器交互。它们支 持资源适配器和客户机应用程序之间由客户机应用程序发起的同步通信。

JCA 1.5

JCA 1.5 是最新的规范,它定义了 JCA 1.0 中缺失的部分。除了上面提到的 JCA 1.0 契约之外,JCA 1.5 还定义了管理契约和入站契 约。我们来看看这些新契约。

JCA 1.5 所支持契约使用不同的分类,如下所示。

出站契约: JCA 1.0 部分 中指定的三种契约 —— 连接、事务和安全性管理。这些契约主要支持从资源适配器到 EIS 并返回的同步 出站通信。

入站契约:支持从 EIS 到资源适配器的入站连通性,这是通过提供插入消息提供者和导入事务的功能实现的。

消息流入:支持一个资源适配器和任何消息提供者之间的交互。该契约扩展了 MDB 的定义。MDB 不再局限于接收 JMS 消息。根据这一 契约,MDB 能够侦听来自一个资源适配器的任何类型的消息。

事务流入:支持从一个 EIS 到一个资源适配器的事务导入。导入事务之后,资源适配器应将这这些事务传播到应用服务器,以启用崩 溃还原、完成事务处理等。该契约提供了确保应用服务器为传入的事务保留 ACID(原子性、一致性、独立性和持久性)属性的功能。

管理契约:支持适配器生命周期管理和线程管理。

生命周期管理:为一个资源适配器定义生命周期方法,类似于 servlet 或 EJB。这些方法由应用服务器在各种事件中调用,例如在适 配器部署或服务器宕机期间。

工作或线程管理:为 J2EE 应用服务器指定一种处理和管理资源适配器线程的安全的机制。它还使应用服务器能够提供线程池或线程的 可重用性,从而提高性能,因为线程可能是资源密集的。

图 1 展示了 JCA 契约和资源适配器与其他 J2EE 组件之间的关系。

图 1. 资源适配器

接下来介绍资源适配器的不同类型。

资源适配器类型

资源适配器的两种基本类型就是出站 和入站。

出站资源适配器

出站资源适配器支持资源适配器和 EIS 之间的来回同步通信。资源适配器首先向 EIS 发送一条请求,EIS 处理请求,并构建一条响应 ,然后将响应回发给资源适配器,如 图 2 所示。

图 2. 资源适配器和 EIS 之间的同步通信

在 图 2 中可以看到,向 EIS 发送了一条请求后(1),资源适配器等待接收 EIS 的响应(2)。这是双向的同步通信。

入站资源适配器

入站资源适配器通过传入资源适配器的数据支持 EIS 和资源适配器之间的异步通信,因而得名入站。在大多数情况下,资源适配器作 为 EIS 上特定事件(例如,添加一个新客户账户时)的侦听器注册到 EIS。当此类事件发生时,后台 EIS 向所有已注册的侦听器发出关 于新客户账户的通知。这个过程属于 EIS 提供侦听器注册机制的功能。图 3 展示了 EIS 和资源适配器间的消息流。

图 3. 资源适配器和 EIS 之间的异步通信

在这种情况下,资源适配器产生一个线程,在 EIS 上注册并侦听 EIS 的事件,这些事件应符合工作管理契约。适配器不等待接收 EIS 的响应,因而这是 EIS 和资源适配器之间的异步通信。

两种类型的资源适配器可以在一个应用程序中并存,具体要取决于您的应用程序的需求。您可使用出站适配器、入站适配器,也可同时 使用这两种类型的资源适配器。

应用程序设计

既然您已经熟悉了 JCA、其契约和适配器的各种类型,那么就可以继续开发示例应用程序中缺少的部分了,也就是一个 JCA 资源适配 器。在这一部分中,您将构建一个与 Apache James 通信、基于 JCA 1.5 的资源适配器,并在 Geronimo 中部署这个适配器。James 适配 器将在 James 中处理假想企业 Foo, Inc. 员工发送的所有经过授权的采购请求电子邮件。

回顾 第 1 部分 中的应用程序设计部分,这可以帮助您选择正确的适配器类型,并确定资源适配器的职责。

用例和组件复习

在 第 1 部分 和 第 2 部分 中,您为示例应用程序构建了各种 J2EE 组件,以处理以下需求:

示例应用程序需要处理员工传入的采购请求电子邮件,并将其移动到可由采购部门访问的指定文件夹中。

应用程序随后读取请求,将检查员工是否确实来自 Foo, Inc。

一旦经过授权,将创建新采购订单,以提交给厂商。

在第 1 部分中,您构建了一个 MDB,在接收到一条 JMS 消息时,它将调用一个 实体 EJB 在数据库中创建新采购订单(上述第 3 条 需求)。

在第 2 部分中,您编写了一个电子邮件应用程序(mailet 和 matcher),并将其部署到了 Apache James 中。这个电子邮件应用程序 处理所有传入的电子邮件,进行检查,确定电子邮件发送者确实经过授权(上述第 1 条需求)。

在这一期中,您将构建示例应用程序的组件,将第 1 部分和第 2 部分中的理念汇总在一起。您将为 Apache James 服务器(EIS)构 建一个资源适配器,它将部署到 Geronimo 服务器中。James 适配器处理所有经过电子邮件应用程序授权的采购请求电子邮件。构建资源 适配器的用例和需求如下:

用例:检查采购请求电子邮件。

需求:应用程序将连续检查等待处理的新采购请求电子邮件。

既然已经确定了适配器的业务流程,那么就可以来评估哪种类型的适配器更适合您的需求了。

出站还是入站适配器?

按照应用程序的需求,您的资源适配器组件必须与一个 MDB 交互。在接收到电子邮件之后,资源适配器需要向 MDB 发送一条消息。 MDB 随后向一个实体 EJB 发送请求,从而在 Purchase Order 数据库中创建一个新条目。

入站适配器的消息流入契约明确地为资源适配器和 MDB 之间的这种交互作用提供了支持。确定适配器的类型之后,就可以开发入站适 配器了。

构建入站适配器

您的入站适配器在 Geronimo 内运行,应能够向 Apache James 服务器注册,并接收所有经过授权的电子邮件。接收电子邮件后,适配 器随之应向 MDB 发送一条带有创建新采购订单请求的消息。

如前所述,只有在 EIS 提供支持的情况下,适配器才能向 EIS 注册。在本例中,James 使用 POP3 协议,该协议不支持注册侦听器( 在其他一些情况下,EIS 不允许注册侦听器)。资源适配器必须定期轮询 James(EIS),检查收件箱中是否有新电子邮件消息。这就叫做 Pull/Poll 机制。

在 Geronimo 服务器中部署的过程中,资源适配器将启动一个轮询线程,定期检查 James 上的电子邮件收件箱。生命周期和工作管理 契约定义了这个过程。要构建入站适配器,您必须实现工作管理、生命周期管理和与这些契约相关的消息流入契约。

让我们来看看这些契约定义的基本接口和适配器实现。

生命周期管理契约

生命周期管理契约为一个资源适配器定义了一组生命周期方法。J2EE 应用服务器在各种事件上调用这些方法,如适配器部署、服务器 宕机等等。

ResourceAdapter 接口

资源适配器将 javax.resource.spi.ResourceAdapter 接口作为 Java bean 实现,在描述符(ra.xml)中指定实现类名。

ResourceAdapter 中定义的各生命周期方法包括:

start(BootstrapContext):在部署资源适配器或应用服务器启动时,应用服务器调用 start 方法。资源适配器可从 BootstrapContext 获得 WorkManager,在 start 方法中启动工作或在激活端点时其工作。本例中的消息端点是一个使用来自资源适配器 的消息的应用服务器。

stop():stop 方法将在适配器被解除部署或应用服务器宕机期间被调用。

endpointActivation():应用服务器在消息端点被激活(例如,初始化了一个 MDB 时)时调用此方法。

endpointDeactivation():应用服务器在消息端点停用时调用此方法。

examples.po.adapter.spi.JamesResourceAdapter 实现 ResourceAdapter 接口中定义的所有生命周期方法,如 清单 1 所示。

清单 1. JamesResourceAdapter 片段

public JamesResourceAdapter() {  super();  }  public void start(BootstrapContext arg0)   throws ResourceAdapterInternalException {  ilog("In start()");  workMgr = arg0.getWorkManager();  }  public void stop() {  ilog("In stop()");  }  public void endpointActivation(MessageEndpointFacTory arg0,   ActivationSpec arg1) throws ResourceException {  ilog("In endpointActivation");emailWork = new JamesWork(emailSpec.getEmailUser(),  emailSpec  .getEmailPassword(),  emailSpec.getEmailHost(),  arg0);  //starts polling the email inbox     workMgr.startWork(emailWork);  }  public void endpointDeactivation(MessageEndpointFacTory arg0,   ActivationSpec arg1) {  //stops polling email inboxemailWork.release();  }

如 清单 1 所示,JamesResourceAdapter 在消息端点被激活时初始化并启动对 James 服务器上电子邮件收件箱的轮询。

要部署一个资源适配器,需要两个描述符(类似于 EJB):ra.xml(标准 J2EE 描述符)和 geronimo-ra.xml(Apache Geronimo 应用 服务器特定的描述符)。现在来看部署描述符(geronimo-ra.xml)中配置 JamesResourceAdapter 的部分(参见 清单 2)。

清单 2. JamesResourceAdapter 的 ra.xml

examples.po.adapter.spi.JamesResourceAdapter

请注意 ra.xml 是如何为 ResourceAdapter 定义实现类的,参见 清单 3。

清单 3. JamesResourceAdapter 的 geronimo-ra.xml

James Inbound Resource  AdapterDefaultWorkManager

geronimo-ra.xml 描述符定义了一个默认的 WorkManager,在启动时,它将随 BootStrapContext 一起传递给 JamesResourceAdapter 。

ActivationSpec 接口

资源适配器应将 javax.resource.spi.ActivationSpec 接口实现为 Java bean。它包含特定于 EIS 的属性,用以激活消息端点。这些 属性是在适配器的部署描述符(ra.xml)中指定的。

Validate() 方法可用于验证 activationspec 属性。它通常由部署工具这样的工具使用。

examples.po.adapter.spi.JamesActivationSpec 具有 清单 4 中所示属性的一组 getter 和 setter 方法。

清单 4. JamesActivationSpec 片段

public class JamesActivationSpec implements ActivationSpec, Serializable {  private String emailUser = null;  private String emailPassword = null;  private String emailHost = null;  private ResourceAdapter rar = null;

ra.xml 定义了 ActivationSpec 初始化一个消息端点所需的全部属性(参见 清单 5)。

清单 5. ra.xml 中显示 ActivationSpec 属性的片段

        examples.po.mdb.JamesMessageListener      examples.po.adapter.spi.JamesActivationSpec         emailHost             emailUser             emailPassword               

JamesActivationSpec 的属性是 e-mail host、e-mail user name 和 e-mail password。这些属性是轮询 James 服务器上电子邮件收 件箱时必需的。

工作管理契约

现在您需要实现工作管理契约。

Work 接口

javax.resource.spi.work.Work 接口建模一个可由 WorkManager 管理的工作实体。这是一个线程,James 资源适配器使用它在 James 服务器上轮询电子邮件。

Work 接口包含以下两个方法:

release():该方法标记工作完成。

run():由于 Work 是一个线程,因此有一个 run() 方法,该方法具有线程的实际执行。

examples.po.adapter.spi.JamesWork 是一个线程,用于轮询 James 服务器上的电子邮件。它具有 清单 6 中所示属性的一组 getter 和 setter 方法。

清单 6. JamesWork 片段

public void run() {     // periodically check for unread emails.     try {        while (poll) {           ilog("Polling for new emails ... ");           // read the messages and delete them           inbox.open(Folder.READ_WRITE);           int totalMessages = inbox.getMessageCount();           if (totalMessages == 0) {              ilog(inbox + " is empty");           } else {              // Get Messages              Message[] messages = inbox.getMessages();              // Process each message           for (int i = 0; i < messages.length; i++) {           MimeMessage mime = (MimeMessage) messages[i];           processMsg(mime);           mime.setFlag(Flags.Flag.DELETED, true);           }         }         // close inbox and main sTore         inbox.close(true);         mailSTore.close();         // Wait         ilog("Waiting for 15 sec ...............");         Thread.sleep(15000);       }// end while    } catch (Exception e) {       e.printStackTrace();       throw new IllegalStateException("" + e);}}// end runprivate void processMsg(MimeMessage msg) {    ilog("In Process Message : " + msg);    try {       MessageEndpoint msgEndPoint = facTory.createEndpoint(null);       ilog("Message End Point is: " + msgEndPoint);       // msgEndpoint is an app server proxy       if (msg != null) {         JamesMessage email = new JamesMessage();         PurchaseOrderBean pobean = createPurchaseOrderBean(msg);         //set PurchaseOrder bean         email.setPoBean(pobean);         //ilog("Before posting Message to End Point");         ((JamesMessageListener) msgEndPoint).onMessage(email);         ilog("Message is posted to End Point");       }    } catch (Exception e) {       e.printStackTrace();    }}

如 清单 6 所示,run() 方法周期性地轮询 James 服务器上的电子邮件收件箱。如果发现电子邮件,它就会调用 processMsg(),创建 一个名为 JamesMessage 的新消息。它还会解析电子邮件内容,以创建 PurchaseOrderBean、createPurchaseOrderBean()。它随后调用端 点上的 onMessage() 消息。

WorkManager 接口

资源适配器不需要实现 javax.resource.spi.work.WorkManager 接口。就本教程的目的而言,您将使用 Geronimo 中的 DefaultWorkManager。工作管理器实现类是在部署描述符(ra.xml)中设置的。

图 4 说明了入站资源适配器部署过程中的事件顺序和数据流。

图 4. 资源适配器部署序列图

在适配器部署过程中,应用服务器创建 ResourceAdapter 的一个新实例,并调用 start() 方法。ResourceAdapter 可能会初始化所需 的任何资源,以便此后进行处理。在 start() 方法中,ResourceAdapter 接收 BootStrapContext 对象,它可用于检索 WorkManager。 ResourceAdapter 可使用 WorkManager 提交工作,WorkManager 随后又启动工作线程。ResourceAdapter 的 stop() 方法将在应用服务器 宕机或资源适配器解除部署时调用。

消息流入契约

为实现消息流入契约,您的 James 资源适配器定义了一个名为 JamesMessageListener 的自定义 MessageListener 和一个名为 JamesMessage 的自定义消息类型。您将修改 PurchaseOrderMDB,来侦听 James 资源适配器定义的自定义类型的消息。

消息侦听器和消息类型

James 资源适配器定义了一个自定义的消息侦听器名称 —— JamesMessageListener,如 清单 7 所示。它还定义了一个名为 JamesMessage 的自定义消息类型。消息侦听器侦听来自 James 资源适配器(本例中是 PurchaseOrderMDB)的消息。您必须实现自定义消 息侦听器,这样才能接收来自资源适配器的消息。

清单 7. JamesListener

public interface JamesMessageListener {  public void onMessage(JamesMessage email);}

JamesMessageListener 接口仅定义了一个 onMessage() 方法。此方法将被 James 资源适配器调用(参见 清单 8)。

清单 8. JamesMessage

public class JamesMessage {  private PurchaseOrderBean poBean = null;  public JamesMessage() {  super();  }  public PurchaseOrderBean getPoBean() {  return poBean;  }  public void setPoBean(PurchaseOrderBean poBean) {  this.poBean = poBean;  }

JamesMessage 具有 PurchaseOrderBean,可将其传递给实体 bean 来在数据库中创建一个新采购订单条目。

消息驱动 bean

MDB 就是一个将被应用服务器(Geronimo)调用的消息端点。回顾您在本系列第 1 部分中构建的 PurchaseOrderMDB。您实现了 PurchaseOrderMDB 来侦听 JMS 消息。为实现消息流入,您将修改 PurchaseOrderMDB,以便侦听自定义侦听器类型的消息,假设是 JamesMessageListener。清单 9 显示了应为 PurchaseOrderMDB 实现的更改。

清单 9. 修订后的 PurchaseOrderMDB

Part 1:  public class PurchaseOrderMDB  implements MessageDrivenBean, MessageListener {Revised:  public class PurchaseOrderMDB  implements MessageDrivenBean, MessageListener, JamesMessageListener {Part 1:  There was no method for JamesMessgeListenerRevised:  public void onMessage(JamesMessage email) {  Logger log = Logger.getLogger("PurchaseOrderMDB");  log.info("Received James Message : "+ email);  PurchaseOrderBean poBean = null;  try {   poBean = email.getPoBean();   log.info("A new Purchase Order will be added to POSystem.");   addPurchaseOrder(poBean);  } catch (Exception e) {   e.printStackTrace();   log.severe(""+e);  }  }//end onMessage(JamesMessage)

在 清单 9 中,请注意本系列 第 1 部分 中的内容与更改后的内容之间的差别:PurchaseOrderMDB 实现 JamesMessageListener,从 而实现了 onMessage(JamesMessage)。在 下载 中获得的 $part3.zip/src/examples/po/mdb 中可找到修改后的 PurchaseOrderMDB 的源 文件。

由于修改了 PurchaseOrderMDB,所以您必须对部署描述符作出相应的修改。共有两个部署描述符,分别是 ejb-jar.xml 和 openejb- jar.xml,修改如 清单 10 所示。

清单 10. MDB 部署描述符(openejb-jar.xml)的修改

Part 1:PurchaseOrderMDB  geronimo.server:J2EEApplication=null,J2EEServer=geronimo,JCAResource=geronimo/activemq/1.0/car,j2eeType=JCAResourceAdapter,name=ActiveMQ RAdestinationPOTopicdestinationTypejavax.jms.Topic            ejb/PurchaseOrderEJB       PurchaseOrderEJB       Revised Part 3:  PurchaseOrderMDBJames Inbound Resource AdapteremailHostlocalhostemailUserauthorized-ordersemailPasswordpassword

在 清单 10 中,您可看到第 1 部分中的 PurchaseOrderMDB 是如何侦听 POTopic 的 JMS 消息的,但在这里发生了变化,MDB 现在侦 听的是来自 James 资源适配器的消息。

让我们看一下 ejb-jar.xml 描述符(参见 清单 11)。

清单 11. ejb-jar.xml

Part 1:          PurchaseOrderMDB      examples.po.mdb.PurchaseOrderMDB      Container  javax.jms.Topic    Part 3:PurchaseOrderMDBexamples.po.mdb.PurchaseOrderMDB  examples.po.mdb.JamesMessageListenerContaineremailHostlocalhostemailUserauthorized-orders  emailPasswordpassword

第 1 部分中的 ejb-jar.xml 描述符(如 清单 11 所示)描述,PurchaseOrderMDB 侦听来自一个 JMS 资源的 JMS 消息 —— 也就是 Topic。在本教程中,您修改了描述符,将 JamesMessageListener 作为 MDB 的侦听器类型包含进来。新的描述符还包含了 JamesActivationSpec 属性。

图 5 展示了在一个消息端点(本例中是一个 MDB)部署过程中发生的活动的顺序。

图 5. 部署一个消息端点

如 图 5 所示,在 MDB 部署过程中,J2EE 应用服务器使用部署描述符中的属性实例化并配置 ActivationSpec。随后它实例化 MessageEndpointFacTory,并调用 ResourceAdapter 的 endpointActivation() 方法。该方法主要负责向 Message 使用者交付消息。

在示例应用程序中,您在 JamesResourceAdapter 的 endpointActivation() 方法中启动了 JamesWork 线程,它又启动了对 James 服 务器上电子邮件消息的轮询。如果在 James 服务器上找到了任何电子邮件,JamesWork 就会处理电子邮件,如 清单 12 所示。

清单 12. processMsg 方法的 JamesWork 线程片段

private void processMsg(MimeMessage msg) {  MessageEndpoint msgEndPoint = facTory.createEndpoint(null);  PurchaseOrderBean pobean = createPurchaseOrderBean(msg);JamesMessage email = new JamesMessage();  //set PurchaseOrder bean  email.setPoBean(pobean);  ((JamesMessageListener) msgEndPoint).onMessage(email);

JamesWork 使用 MessageEndointFacTory 来创建端点(在本例中,端点是一个 MDB),然后通过电子邮件消息创建一个新 PurchaseOrderBean,在新的 JamesMessage 中设置 PurchaseOrderBean,最后通过调用 MDB 上的 onMessage() 方法将消息传递到端点。

本教程的 下载 文件 part3.zip 中包含 James 资源适配器、PurchaseOrderMDB 以及 ra.xml 和 geronimo-ra.xml 描述符的所有源文 件(.java)。

除了描述符(ra.xml 和 geronimo-ra.xml)之外,无需对本教程中的资源适配器进行其他任何配置。但要使整个应用程序运作起来, 请查看本系列 第 1 部分 和 第 2 部分 中配置部分的内容,确保 Apache James 和 Geronimo 已配置。

部署

现在您已经完成了全部配置,是时候部署您的示例应用程序了。看看示例应用程序的打包。

部署示例应用程序

您将以 .ear 文件的形式部署示例应用程序,.ear 表示企业归档文件。.ear 文件需要两个描述符:application.xml 和特定于应用服 务器的 .xml 文件(在本例中是 geronimo-application.xml),如 清单 13 所示。

清单 13. application.xml 描述符

  ExampleApp     po-ejb.jar james.rar

如 清单 13 所示,.ear 文件包含两个模块:EJB 和资源适配器。资源适配器打包为 .rar 文件,其中 RAR 表示资源适配器归档。

因而,po-ejb.jar 包含 PurchaseOrderEJB 和 PurchaseOrderMDB,而 james.rar 是 James 资源适配器(参见 清单 14)。

清单 14. geronimo-application.xml

    po-ejb.jar    dds/openejb-jar.xml       james.rar  dds/geronimo-ra.xml 

geronimo-application.xml 描述符具有对 EJB 和连接器的引用。它还指定了特定于 Apache Geronimo 的描述符所在的位置,位于 .ear 文件的 names dds 目录中。

让我们开始部署过程。

第 1 步:解除部署第 1 部分的 EJB JAR

如果您未按照第 1 部分中的介绍部署 po-ejb.jar,则可跳过这一步。

将第 1 部分中部署的 po-ejb.jar 解除部署,如 图 6 所示。

图 6. 解除部署 PurchaseOrderEJB

通过 $GERONIMO_HOME/bin/ startup.bat 启动 Geronimo。

在 http://localhost.com:8080/console 访问 Geronimo 控制台(默认用户 ID 是 system,password 是 manager)。

单击左侧的 EJB JARS,您将看到 PurchaseOrderEJB。

单击 Uninstall 来解除部署 PurchaseOrderEJB 和 PurchaseOrderMDB。您会将这些 J2EE 组件作为 EAR 文件的一部分部署。

第 2 步:检查 POMailet 和 POMatcher。

确保在 James 中部署了第 2 部分介绍的电子邮件应用程序,确保 James 正在运行。

第 3 步:部署 .ear 文件

确认了 Apache James 和 Geronimo 服务器都在运行之后,您就可以部署 po.ear 文件了。这个文件位于 $part3.zip/deploy 目录下 。

在 http://localhost:8080/console 访问 Geronimo 服务器控制台(默认用户 ID 是 system,password 是 manager)。

在左侧单击 Deploy New,您将看到类似于 图 7 的屏幕。

图 7. Apache Geronimo 的部署控制台

单击 Archive 旁的 Browse 按钮,在 $part3.zip/deploy 目录中选择 po.ear 文件,如 图 8 所示。

图 8. Apache Geronimo 的 部署控制台

选中 po.ear 文件之后,单击 Install。Apache Geronimo 现在将部署 po.ear 文件内的描述符中类出的所有模块,若部署成功,您将 看到一条消息,如 图 9 所示。

图 9. EAR 在 Geronimo 中部署成功

成功部署了 po.ear 文件之后,您将在 Geronimo 控制台中看到一些日志记录,如 图 10 所示。

图 10. 部署后的 Geronimo 控制台

如您所见,适配器及其端点(PurchaseOrderMDB)部署好之后,JamesResourceAdapter endpointActivated() 方法将被 Geronimo 调 用,您的适配器就会开始周期性地(每 15 秒)为用户 authorized-orders 轮询 James 服务器中的电子邮件消息。但如 图 10 所示, INBOX is empty 消息表示名为 authorized-orders 的用户的收件箱中没有任何电子邮件。

您已成功地部署了示例应用程序,其中包括 MDB、实体 EJB 和一个用于 Apache James 的资源适配器。现在您可以创建一个客户机或 测试程序来测试您的应用程序了。

测试应用程序

您不必编写一个全新的测试客户机。只要将为 第 2 部分 编写的 EmailClient.java 客户机梢加修改即可。EmailClient 能够发送电 子邮件以及读取用户收件箱中的电子邮件。

您将修改这个 EmailClient,以发送将采购请求数据作为电子邮件主体的电子邮件消息。

电子邮件消息格式

首先,您应决定一种标准格式,以便 James 资源适配器轻松解析电子邮件消息,并构建 PurchaseOrderBean 来将其发送到 PurchaseOrderMDB。电子邮件消息的格式如 清单 15 所示。

清单 15. 电子邮件消息格式

Item=Pens;Description=Low on stock for Marketing Department;Price=10;Quantity=250;

客户机

客户机程序 EmailClient.java(参见 清单 16)提供发送电子邮件的方法。

可在 $part3.home/src/examples/po/test 目录下查看整个客户机程序。

清单 16. 电子邮件客户机的 sendEmail 方法

/**  * Sends an email message  *  * @param from Sender's email address  * @param to Recipient's email address  * @param subject Email Subject  * @param content Email Message Content  * @throws MessagingException  */public void sendEmail(String from,   String to,   String subject,   String content)  throws MessagingException {  Session session = createSession(host, null, null);  MimeMessage msg = new MimeMessage(session);  msg.setFrom(new InternetAddress(from));  msg.addRecipients(Message.RecipientType.TO, to);  msg.setSubject(subject);  msg.setText(content);  Transport.send(msg);  System.out.println("Email message is successfully sent to " + to);}

在 sendEmail 方法中,您创建了一个新电子邮件消息(MimeMessage),带有指定的发件人电子邮件地址(from)、收件人电子邮件地 址(to)、主题(Subject)和消息主体(content)。调用 Transport.send() 将消息实际传递到收件人的收件箱。您将使用此方法来发 送采购请求电子邮件。

现在来看一下建立电子邮件消息的 main() 方法,如 清单 17 所示。

清单 17. 电子邮件客户机的 main 方法

public static void main(String a[]) throws Exception {  //Purchase Order Properties  String ITEM = "Pens";  String DESCRIPTION = "Low on stock for Marketing Department";  String UNITPRICE = "2";  String QUANTITY="250";  //check if from and to have been provided  String mode = System.getProperty("mode");  String host = null;  String user = null;  String password = null;  String from = null;  String to = null;  String subject = "Purchase Request";  Calendar cal = Calendar.getInstance();     String content = "Item="+ITEM+";"+     "Description="+DESCRIPTION+";"+     "Price="+UNITPRICE+";"+     "Quantity="+QUANTITY;....EmailClient client = new EmailClient(host);  if(isSend)   client.sendEmail(from, to, subject, content);  else   client.readInbox(user, password);  }//end main

main() 方法接受几个用于发送和读取电子邮件的命令行参数。如 清单 17 所示,sendEmail() 方法需要电子邮件主机、from 和 to 值,readInbox() 需要电子邮件主机以及一个电子邮件账户的用户名和密码。

main() 方法负责构建电子邮件消息,采用 清单 15 中指定的格式,随后将电子邮件发送到 orders@localhost.com。 POMailet/POMatcher 将获取此电子邮件,若发件人经过授权,电子邮件将转移到另外一个名为 authorized-orders 的文件夹中。

现在 James 资源适配器轮询此文件夹,一旦检测到电子邮件,即获取邮件、解析电子邮件消息、创建 PurchaseOrderBean,并将 JamesMessage 发送给 PurchaseOrderMDB。这个 MDB 随后调用 PurchaseOrderEJB,这是一个实体 bean,将在 Geronimo Derby 数据库表 中创建一个新采购订单条目。

运行测试

您将使用 part3.zip 文件 中提供的 runSendEmail.cmd 脚本来测试整个应用程序流。如果您按照本教程中的说明配置了应用程序,那 么只需要修改 JAVA_HOME 来运行这些测试即可。

现在,若 Apache James 和 Apache Geronimo 服务器尚未运行,启动它们。

运行测试之前,先看一下 PurchaseOrder 表(参见 图 11)。

图 11. 测试之前的 PurchaseOrder 表内容

可以看到,PurchaseOrder 表中没有任何条目。

现在您就可以运行 runSendEmail.cmd 脚本,从电子邮件地址 user2@localhost.com(记住,user1@localhost.com 是经过授权的发件 人)向 orders@localhost.com(这个地址可在 runSendEmail.cmd 中修改,方法是修改 EMAIL_TO 参数)发送采购订单电子邮件。

在运行 runSendEmail.cmd 之后,您将看到一些日志记录,如 图 12 所示。

图 12. 通过运行 runSendEmail.cmd 测试应用程序

如果您看到如 图 12 所示的消息,就表示电子邮件已成功提交。这个传入 James 服务器的电子邮件将由您的 POMatcher-POMailet 处 理。

James 服务器首先调用 POMatcher 的 match() 方法。由于发件人 user1@localhost.com 是一名经过授权的发件人,因此 POMailet 的 service() 方法将被调用,电子邮件应已转发到 authorized-orders@localhost.com。您一定感到疑惑,为什么再次调用了 POMatcher match()?(参见 图 13)。

图 13. James 服务器的控制台日志

POMatcher match() 被再次调用的原因是您的 POMailet 的 service() 方法向 authorized-orders@localhost.com 发送了一封电子邮 件。这封电子邮件在 James 服务器内的处理方式与其他任何传入的电子邮件都相同,因此服务器调用 POMatcher match() 方法。

提示:这就是在将电子邮件转发到 authorized-orders@localhost.com 之前,将 from 更改为 root@localhost.com 或其他未经授权 的发件人的原因,目的在于避免出现无穷递归循环。这是一个小技巧,因为 POP3 协议不支持电子邮件在文件夹之前的移动(IMAP 支持此 功能)。

现在,由于发件人的电子邮件地址是 root@localhost.com,不是一个经过授权的发件人,因此 POMailet 不会处理这封转发过来的电 子邮件。

检查一下,POMailet 是否确实将电子邮件传递到了 authorized-orders@localhost.com。

运行 runReadEmail.cmd,您将看到如 图 14 所示的结果。

图 14. 显示电子邮件

显示了 authorized-orders 收件箱中的电子邮件。

如果您查看 Geronimo 控制台,您将看到来自 JamesWork 的日志消息,如 图 15 所示。

图 15. 发送一封电子邮件后的 Geronimo 控制台

运行在 Geronimo 中的 JamesWork 线程接收到了电子邮件消息,它将此消息解析为 Item、Description、Unit Price 和 Quantity, 从而创建一个 PurchaseOrderBean。这个 bean 随后将打包到 JamesMessage 中,并发送给 PurchaseOrderMDB,PurchaseOrderMDB 调用 PurchaseOrderEJB 在数据库中创建一个采购订单。

查看结果

现在您可以看看 PurchaseOrderEJB 是否在 Derby 数据库中输入了一个新的采购订单。如果您转到 Geronimo Web 控制台并访问左侧 导航窗格中显示的 DBManager,可看到 PurchaseOrder 表的内容,如 图 16 所示。

图 16. Geronimo Web 控制台 DBManager

瞧!在表中有一个采购订单的条目。

结束语

祝贺您!示例应用程序至此已经完成了。在这个系列教程中,您学习了使用 MDB、实体 bean、Mailet、Matcher 和资源适配器构建集 成解决方案。您还了解了 Apache Geronimo 中的各种配置和部署以及 James 服务器。现在您算得上是 Java 专业人士了!

本文配套源码

不要做刺猬能不与人结仇就不与人结仇,

使您的应用程序调用我的应用程序,第3部分:资源适配器

相关文章:

你感兴趣的文章:

标签云: