UML软件工程组织

通过JAVA/CORBA访问Domino
文章作者:薛谷雨 文章来源:IBM

Lotus Domino 作为一种全球领先的协作、消息传递和Web支持软件,正在迅速地在中国企事业推广。Domino已经成为帮助每个人更灵活和更高效地工作的强大支持。如何从DOMINO数据库中获取数据,使这些数据为其他系统可用,已经成为许多企业迫切需要解决的问题。然而domino不同于普通的关系型数据库,由 ibm/lotus自主研发,有自己的标准和特殊性,是一种另类的数据库类型。开发具有访问DOMINO服务器的应用程序的方法有许多种,但是普遍存在的问题是功能有很大的局限性,都要在依赖于lotous notes这一庞大而昂贵的客户端软件。随着java语言和CORBA中间件技术的日趋成熟,新版本的Domino也提供了 corba服务,使这一问题得到彻底的解决。java/corba访问domino的优点在于采用此技术开发的应用程序在不用安装lotus notes的情况下获取远程 domino服务器上的数据,真正做到了瘦客户端,为企业节省了不必要的开支,同时也极大的降低了应用程序部署的难度。由于这一技术刚刚发展两三年,国内基本上没有相应的中文资料,corba技术也属于比较高级的开发技术,绝大多数开发工程师都没有接触过,因此许多做数据集成的开发人员对domino的开发望而生畏。本文旨在为开发人员提供一个全面的java/corba访问domino的技术解决方案,并通过例程指导开发人员掌握这一新技术。其中也有笔者在开发过程中的一些经验与教训,相信对广大开发人员一定会有相当大的帮助。

访问DOMINO的方式

本地调用

DOMINO CLASSES方式

通过使用lotus提供的domino classes通过本地安装的lotus notes访问远程domino服务器。此方法的缺点在于应用程序所在的主机需要安装lotus notes,这在UNIX系列平台上或某些项目实施过程中是不允许的。

JDBC方式

JDBC方法是使用标准 Java 数据库技术 Domino 提供 JDBC 驱动程序并且它的行为可以看起来和标准关系数据库一样。但是,尽管某些 SQL 扩展允许访问其有层次结构的数据,但这种方法限制了使用 Domino 所能提供的好处。

你可以去LOTUS的官方网站下载DOMINO的JDBC驱动:

http://www.lotus.com/products/rnext.nsf/873769A79D9C5B2285256A0800720B96/D146 69BE33B75CB585256C4700659FDC?OpenDocument

远程访问

EJB访问方式

通过websphere中的ejb方式访问远程domino服务器,方便快捷,缺点是需要websphere作为应用服务器,对软件平台和硬件都有一定的要求。

JAVA/CORBA方式

一种彻底的解决方案,基于IIOP协议,采用JAVA/CORBA方式开发的应用程序的运行环境中无须安装lotus notes便可以完全访问控制domino中的数据。

本文重点介绍JAVA/CORBA方式。

CORBA简介

CORBA(Common Object Request Broker Architecture, 公共对象请求代理体系结构)是由OMG(对象管理组织,Object Management Group)提出的应用软件体系结构和对象技术规范,其核心是一套标准的语言、接口和协议,以支持异构分布应用程序间的互操作性及独立于平台和编程语言的对象重用。

CORBA规范充分利用了现今软件技术发展的最新成果,在基于网络的分布式应用环境下实现应用软件的集成,使得面向对象的软件在分布、异构环境下实现可重用、可移植和互操作。

使用IBM提供的CORBA库访问DOMINO

你可以去下面地址下载IBM提供的针对于JAVA/CORBA的LOTUS工具库:

http://www-10.lotus.com/ldd/toolk its 开发人员通使用这个库,可以协助你建立:

访问本地或远程的domino数据和服务的JAVA应用程序;

访问关系型数据库中数据的JAVA应用程序;

通过CORBA,IIOP,IDL访问远程domino对象的JAVA/C++应用程序。

此工具库中带有丰富的文档范例,你完全可以通过这些范例掌握工具库的使用方法,在此不再赘述。但是,IBM提供的CORBA库dtjava2.1中存在一些BUG,在Domino 5.0.2 or 5.0.3中某些功能不能正常工作,使用中还存在一些其它问题,如果你感觉使用起来不够方便的话,此篇文章正是解你燃眉之急,指导你如何创建并使用通过自己的domino corba库访问domino对象!

根据IDL文件创建自己的CORBA库

首先你所需要的是一个描述domino 对象的IDL文件,这个文件在上面提到的工具库的IDL目录下,名称为ncorba.idl 。既然使用corba,就要有请求对象代理(ORB,Object Request Broker)的辅助,许多厂商都提供了orb工具,比较有名的有 visiBroker,Jcorb,OpenORB等等。业内公认的比较成熟和完善的当推borland的visiBroker,但由于它是商业软件,且价格不菲,我们便不考虑使用它做ORB。在众多的免费版本ORB中,采用OpenORB作为CORBA是个不错的选择,你可以去www.openorb.org下载OpenORB的最新版本。OpenORB的安装非常简单:解压缩下载的压缩文件到你的硬盘上,在lib子目录下,有九个.jar为后缀的库文件,主要的两个是 openorb-x.x.x.jar和openorb_tools-x.x.x.jar(其中的x.x.x根据是你的下载的openorb的版本号)。第一个jar包作为 openorb的核心,必须安装到每一个运行corba应用程序的主机上。它包含OpenORB在运行时需要的客户端和服务器端的代码。由于OpenORB依赖于 xerces的xml解析器,所以lib目录下的xerces.jar这个文件也要放到和openorb-x.x.x.jar同一个目录下。openorb_tools-x.x.x.jar必须安装到开发主机上,同时它也被openorb的内核所依赖,因此应该也安装到 openorb-x.x.x.jar同一目录下。这九个jar包应该都在你的corba应用程序运行的classpath中,否则你的corba应用程序将不能正确编译通过或执行。

好了,做完以上配置工作,我们就可以使用OpenORB的IDL编译器生成我们自己的domino 对象了。进入上一节提到的ibm的toolkits的idl目录,找到ncorba.idl这个文件,在windows的控制台模式下,输入如下指令:


java org.openorb.compiler.IdlCompiler ncorba.idl

OpenORB的IDL编译器将在当前目录下创建一个名称为generated的目录,将生成的domino对象的java代码都放在这个目录下。

如果执行以上操作不成功,报错有未找到的类,说明你系统的classpath的配置有问题,缺少上面提到的九个jar包中的一个。请重新配置你的 classpath,解决这个问题。

在工作中发现OpenORB的编译器功能并不够强大,对idl中的一些宏定义不能正确转换成符合java语法的代码,你在用jdk提供的javac编译器编译 OpenORB生成的java代码时会发现这个问题,编译器会抛出许多错误。不要着急,你可以用文本编辑工具打开ncorba.idl文件,根据刚才javac编译器提到的java代码中的错误位置,找到idl文件中相对应的宏定义,这些宏定义一般都是简单的加法计算,你手工计算出表达式的值将idl文件中的这些宏替换后存盘退出后,重新执行上面的提到的操作,用org.openorb.compiler.IdlCompiler编译corba.idl文件,此时再生成的java代码将是完全正确的。我想 OpenORB以后新版本的编译器将解决这个问题。

将生成的java代码编译打包成一个DominoCorba.jar文件,我们自己的domino java/corba ToolKits 诞生了!这个库的一大好处是全部开源的(好像是废话),十分有利于我们应用程序的调试跟踪。有了自己的功能强大的库,让我们看看如何利用她开发访问domino的java应用程序吧。

构建自己的CORBA库,完全驾驭DOMINO

开发环境的搭建

首先你的机器上要安装domino 5或domino5以上版本,启动domino服务器,确定domino的HTTP服务和DIIOP(domino IIOP)服务是否启动,这两个服务是必需的。

你可以在domino的控制台输入下面的指令:


>load http                              (启动http服务)
>load diiop                              (启动diiop服务)
停止服务是:
>tell http quit        (停止http服务)
>tell diiop quit        (停止diiop服务)
刷新服务是:
>tell http refresh                          (刷新http服务)
>tell diiop refresh       (刷新diiop服务)

假设在domino服务器上创建一个名称为xg-yfwh.nsf的库文件,后面的例子就是访问这个库中的数据;

假设你的domino的用户名为dev dev/dev, 密码为12345678。

启动Domino Admininstrator,编辑当前服务器文档,在"安全性"一页中,配置右下角的 "Java/COM限制"配置项,结果如图:


在"Internet协议"一页中,选HTTP子项,配置"允许HTTP客户浏览数据"选项为"是",如下图:


重新启动domino服务器,启动HTTP和DIIOP服务。

JAVA/CORBA应用程序的开发

注意:以下的程序开发都将用到前面生成的DominoCorba.jar库及OpenORB库,请配置你的工程的classpath包含这两套库的全部文件。

理解domino对象的结构

domino对象类的结构基于包容模型,包容模型定义了对象的范围。容器对象通常被用来访问它所包含的子对象。例如,你可以用session对象或的 Database或Database的集合;而一个Database对象可以创建一个或若干个Document对象。


关闭一个容器对象意味着其包含的全部子对象也将被关闭。例如,你建立了一个Database对象,并适应它创建了一个Document对象,如果关闭了 Database对象,Document 对象也会随之关闭。如果容器对象超时,它将会被自动关闭,其包含的对象也将被自动关闭。因此你应该在容器对象超时或关闭前保存你的任何改变。

建立连接

客户端java程序向domino服务器发出CORBA请求,服务器通过HTTP协议返回给客户端IOR(Interoperable Object Reference),之后客户端通过IIOP协议与服务器进行通讯。这一握手过程如下图所示:

下面代码实现这一握手过程:


      String dominoHost = "192.168.3.56";//主机名或IP地址
String strIOR = null;
      URL url = new URL("http://" + dominoHost + "/diiop_ior.txt");
      InputStream in = url.openStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(in));
      for (boolean bExit = false; !bExit; ) {
        String line = br.readLine();
        if (line == null) {
          bExit = true;
        }
        else {
          if (strIOR == null)
            strIOR = line;
          else
            strIOR = strIOR + line;
          if (strIOR.startsWith("IOR:"))
            bExit = true;
        }
      }
      br.close();
System.out.println("strIOR - " + strIOR);

如果 屏幕上能够打印出一长串字符,说明握手成功。

创建session

不用我说,从上面的结构图中,你也可以看出,要想通过CORBA同服务器会话,首先一定要创建一个session,这也是最容易出问题的一步。一般来讲,能成功的创建session,后面的困难就不算什么了。

第一步 告知ORB架构,用openorb作为对象请求代理:


Properties props = new Properties();
      props.put("org.omg.CORBA.ORBClass", "org.openorb.CORBA.ORB");

      ORB orb = ORB.init(args, props);

第二步 通过 IOR 得到 IObjectServer 对象


            org.omg.CORBA.Object obj = orb.string_to_object(strIOR);
      System.out.println("org.omg.CORBA.Object - " + obj);

      IObjectServer ios = IObjectServerHelper.narrow(obj);
      System.out.println("IObjectServer - " + ios);
      

第三步 通过 IObjectServer 获得 ISession


      ProtocolVersion maxVersion = new ProtocolVersion(IBase.
          DOM_MAJOR_MINIMUM_VERSION, IBase.DOM_MINOR_MINIMUM_VERSION);
      ProtocolVersion minVersion = new ProtocolVersion(IBase.DOM_MAJOR_VERSION,
          IBase.DOM_MINOR_VERSION); 
SessionData sd = ios.createSession(maxVersion, minVersion, 
"dev dev/dev",//用户名称
                                "12345678");//口令
System.out.println("SessionData - " + sd);

如果能够成功打印出结果,说明你的Session创建成功。

获取文档内容

获取文档内容,要涉及到几个domino对象,现介绍如下:

    DbCache:是一个描述数据库为开的情况下,数据库可用性一组信息集合。
    DCData:结果集对象,类似于JDBC中的ResultSet。
    Idatabase:数据库对象。
    Idocument:文档对象。

通过调用iDatabaseHolder的search方法,我们便可以从数据中很容易的取出符合条件的一组文档对象。对比关系型数据库,就相当于SQL的查询操作。Search方法异常强大,这强大也得益于domino数据库功能的强大,是关系型数据库不可比拟的。Search方法包含三个参数:

formula: 字符串型,会使用domino的设计人员都不会陌生就是domino/notes特有的公式语言,只要你会用这种语言,可以随心所欲的获取各种符合你筛选条件的文档集合,如果你还没有掌握公式语言的写法,建议参考Notes Designer中的帮助,里面有非常详细的介绍;

Datetime:domino的日期时间型函数,此参数指定取出的文档的创建或最后更新的日期要晚于这个日期,为空则无任何限制。在关系型数据库中令人头疼的获取增量更新的问题在domino中根本就不是问题;

Maxdocs:获取符合条件的文档的最大个数。

例:获取名称为xg-yfwh.nsf的数据库中由名称为FrmApp创建的更新日期在2003年10月1日下午三点零二分零二秒修改或创建的前10条文档


//sd就是前面创建的SessionData;
DbCache dbCache = session.getDatabase(sd.serverName, "xg-yfwh.nsf", false);
Idatabase iDatabase = dbCache.db;

lotus.domino.corba.DateTime date=session.createDateTime("2003/10/1 15:02:03");
IDateTime idt=session.createDateTimeObject(date);

DCData dCdata = iDatabase.search("Form=\"FrmApp\"", idt, 10);

遍历结果集中的文档

IdatabaseHolder:Database的操纵器,通过它对Database对象读写数据。

IntHolder:整数型对象,功能是将服务器返回的整数转换成本机整数形式。


IDatabaseHolder idatabaseholder = new IDatabaseHolder();
IntHolder intholder = new IntHolder();
IDocument doc = dCdata.dcObject.getFirstDocMDB(idatabaseholder, intholder);
while (doc != null) {
        ItemData[] id = doc.getData().items;
        for (int i = 0; i %lt; id.length; i++) {
          ItemValue iv = id[i].values;//读取数据
          System.out.print("\t" + iv.StringValue());
          System.out.print("[类型:" + id[i].type + "]");
        }
        System.out.println();
  //获取下一条文档
        doc = dCdata.dcObject.getNextDocMDB(doc, idatabaseholder, intholder);
}

关闭session

关闭session是编程的良好习惯。代码很简单:


session.recycle();

前面已经提到,在session关闭后,其包容的子对象也将被自动关闭。

以上便是java通过corba访问domino数据库的完整过程,下面将提供完成这一过程的完整代码。

一个完整的例子


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;

import org.omg.CORBA.*;
import lotus.domino.corba.*;

public class LotusClient {
  public static void main(String args[]) {
    try {
      String dominoHost = "192.168.3.56";

      lotus.domino.corba.DbCache dbCache;
      lotus.domino.corba.IDatabase iDatabase;
      lotus.domino.corba.DbInfo dbInfo;

      lotus.domino.corba.ViewData viewData;
      lotus.domino.corba.IView iView;

      // 通过 http 获取 IOR
      String strIOR = null;
      URL url = new URL("http://" + dominoHost + "/diiop_ior.txt");
      InputStream in = url.openStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(in));
      for (boolean bExit = false; !bExit; ) {
        String line = br.readLine();
        if (line == null) {
          bExit = true;
        }
        else {
          if (strIOR == null)
            strIOR = line;
          else
            strIOR = strIOR + line;
          if (strIOR.startsWith("IOR:"))
            bExit = true;
        }
      }
      br.close();
      System.out.println("strIOR - " + strIOR);

      // 以 OpenORB 实现 CORBA
      Properties props = new Properties();
      props.put("org.omg.CORBA.ORBClass", "org.openorb.CORBA.ORB");

      ORB orb = ORB.init(args, props);
      System.out.println("org.omg.CORBA.ORB - " + orb);

      // 通过 IOR 得到 IObjectServer 对象
      org.omg.CORBA.Object obj = orb.string_to_object(strIOR);
      System.out.println("org.omg.CORBA.Object - " + obj);

      IObjectServer ios = IObjectServerHelper.narrow(obj);
      System.out.println("IObjectServer - " + ios);

      // 通过 IObjectServer 获得 ISession
      ProtocolVersion maxVersion = new ProtocolVersion(IBase.
          DOM_MAJOR_MINIMUM_VERSION, IBase.DOM_MINOR_MINIMUM_VERSION);
      ProtocolVersion minVersion = new ProtocolVersion(IBase.DOM_MAJOR_VERSION,
          IBase.DOM_MINOR_VERSION);
      SessionData sd = ios.createSession(maxVersion, minVersion, "dev dev/dev",
                                         "12345678");
      System.out.println("SessionData - " + sd);
      ISession session = sd.sesObject;
      System.out.println("ISession - " + session);

      // 使用 ISession
      String str = session.getURL();
      System.out.println("url - " + str);
      dbCache = session.getDatabase(sd.serverName, "xg-yfwh.nsf", false);
      iDatabase = dbCache.db;

      DCData dCdata = iDatabase.search("1=1", null, 10);
      IDatabaseHolder idatabaseholder = new IDatabaseHolder();
      IntHolder intholder = new IntHolder();
      IDocument doc = dCdata.dcObject.getFirstDocMDB(idatabaseholder, 
intholder);

      while (doc != null) {
        ItemData[] id = doc.getData().items;
        for (int i = 0; i %lt; id.length; i++) {
          ItemValue iv = id[i].values;
          System.out.print("\t" + iv.StringValue());
          System.out.print("[类型:" + id[i].type + "]");
        }
        System.out.println();
        doc = dCdata.dcObject.getNextDocMDB(doc, idatabaseholder, intholder);
      }
      System.out.println("query:" + dCdata.query);
      for (int i = 0; i %lt; dCdata.remoteID.length; i++) {
        System.out.println("remoteID:" + dCdata.remoteID);
      }

      while (doc != null) {
        ItemData[] id = doc.getData().items;
        for (int k = 1; k %lt; id.length; k++) {
          ItemValue iv = id[k].values;
          System.out.print("\t" + iv.StringValue());
          System.out.print("[类型:" + id[k].type + "]");
        }
        System.out.println();
      }
      System.out.println("--------------------------------------------");
      session.recycle();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

如何从lotus.domino.NotesException 中获取错误信息

在你编写domino的java/corba应用程序时,不可避免的要同异常处理打交道,NotesException当然是每个开发人员都不愿见到的但又不得不经常面对的问题。

如果你在写程序时尽是简单的采用


try{
 ….
}catch(Exception e){
 e.printStackTrace();
}

的形式捕捉异常,那么一旦代码抛出异常,你将变得一筹莫展,因为屏幕上不会打印出任何对你提供帮助的信息,这方面的资料更是凤毛翎角, 连ibm的官方文档中都没有丰富的错误信息提示。错误捕捉代码应该这样写:


try{
 ….
}catch(Exception e){
if(ex instanceof lotus.domino.NotesException){
System.out.println( "通过CORBA访问数据库发生错误,错误代码为"
+((lotus.domino.NotesException)ex).id);
}
e. printStackTrace();
}

这是屏幕上将打印出错误代码,有了错误代码,我们还要知道错误代码的含义,这就要到IDL文件中去查了。在corba.dll文件中NotesError这个接口定义了全部错误代码(自己要做一下简单的加法才能知道那里面错误代码的具体值),错误代码的变量名正是错误信息。这样你便可以对出现的问题了然于胸了,对你解决突发性事件很有帮助。

针对在JBOSS环境下的解决方案

JBOSS应用服务器作为性能优越的开源J2EE应用服务器产品,已经成为越来越多的中小企业的首选服务器解决方案。当你已经学会上面介绍的开发方法,能够自如的开发自己的JAVA/CORBA应用程序时,你一定会考虑把自己的程序做成JAVABEAN或EJB移植到JBOSS中。很不幸,你会发现,上面这个在标准JAVA环境下可以运行通过的程序,在JBOSS下被调用时不能正确执行。会报文件OpenORB.XM找不到这样的错误。按照屏幕提示的路径org/openorb/config去 openORB.jar文件中看一下,发现OpenORB.XML这个文件好好的呆在那里,怎么会找不到呢?这是个比较隐蔽的问题造成的。在标准的java环境下运行上面的应用程序时,OpenORB也要找这个资源文件,但是他调用默认的classloader,这时是jdk的classloader,可以正常的定位这个资源文件;而在 JBOSS环境下,由于JBOSS的classloader的机制与jdk的classloader机制不同,不能正确的找到这个资源文件,因此便产生了上面这个问题。经过尝试,我的解决方案是:

将例子代码中的ORB orb = ORB.init(args, props)程序行换成下面两行程序:


java.net.URL url=this.getClass().getResource("/org/openorb/");
ORB orb = ORB.init(new String[]{ "-ORBopenorb.home="+url},props);

记住上面两行代码中的第一行代码中的"/org/openorb/"的最后一个字符一定要有"/",否则是错误的。

相信这个这个提示会对你的开发有很大帮助。

可能遇到的错误

1. 在创建SESSION时,服务器拒绝访问

解决办法:先确认你的应用程序中的用户名,密码是否正确。开启Domino Administrator,打开当前服务器文档,切换到"安全性"页,在右下角会出现"JAVA/COM限制"一栏,其中有两个选项:

"运行有限制的Java/Javascript/COM"和"运行无限制的Java/Javascript/COM"。编辑这两个选项,将你JAVA程序中用到的用户名加入到这两个位置,编辑结束存盘退出,重新启动服务器。再次运行JAVA程序,问题排除。

2. 在调用IdbDirectory类的getFirstDatabase()方法时,抛错:lotus.domino.NotesException 错误代码:4536,错误信息:服务器访问被拒绝

解决办法:开启Domino Administrator,打开当前服务器文档,切换到Internet协议一页,将允许HTTP客户浏览数据后面的选项置为"是" 后,编辑结束存盘退出,重起服务器,再次运行JAVA程序,问题排除。

读完此文你会发现通过java/corba访问domino 服务器是如此的简单清晰,在domino的新版本中也在不断的增加domino对java的支持。随着 Domino服务器在企业中应用的普及,这一技术将为企业创造财富发挥更大的威力!

参考资料:

要获得更多关于CORBA的权威资料和最新信息,请到http://www.omg.org/ 查阅。在http://openorb.sourceforge.net/ 可以获得开源项目openorb的全部源代码和开发使用文档。如果您想了解更多通过java连接domino的企业级应用的开发方法,请参阅IBM的红皮书:http://publib-b.boulder.ibm.com/Redbooks.nsf/RedbookAbstracts/sg24542 5.html?Open

关于作者:

薛谷雨是NORDSAN信息科技开发有限公司高级JAVA研发工程师,正致力于企业级异构数据交换的服务器产品的研发,在J2EE和WEB SERVICE方面有较为丰富的开发经验,你可以通过 xgy@sina.com与他取得联系。

原文出自:http://www-900.ibm.com/developerWorks/cn/java/l-corba-domino/index.shtml


版权所有:UML软件工程组织