级别: 初级 刘必欣 (liu_bi_xin@sina.com)
| XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both. |
2002 年 8 月 01 日 ApacheSOAP是目前广泛使用的Web服务开发包,但美中不足的是,它目前并不支持CORBA对象,这使得CORBA开发的应用系统无法利用ApacheSOAP将其业务功能拓展到Internet。本文基于ApcheSOAP2.2介绍一种实现扩展它的功能,使它可以集成CORBA对象提供服务,并希望通过一些尝试性的工作,探讨Web服务与CORBA桥接的部分基本问题和实现策略。
Web services是目前炙手可热的话题,它基于面向服务的体系结构,采用Internet通信协议和XML编码传输消息,使用标准方法进行服务描述、服务发布和服务发现,提供应用级的软件重用和业务集成。作为新一代的面向Internet的应用集成框架,Web服务代表了一种更松散的分布应用结构,能够彻底屏蔽平台差异,在保护现有投资的基础上以最小成本获取企业间最大程度的应用集成和数据交换。
ApacheSOAP是目前广泛使用的Web服务开发包,它采用可扩展插接口的结构,允许接入常规Java类、EJB或COM组件来提供服务。ApacheSOAP既为服务的实现提供了丰富的手段,也为已经存在的大量使用现有技术开发的应用提供了融入新的技术框架的途径。许多商用产品借鉴了ApacheSOAP的工作,而IBM的WSTK则直接将ApacheSOAP及其后继版本Axis作为其运行环境的一部分。
不过,美中不足的是,ApacheSOAP目前不支持CORBA对象,这使得CORBA开发的应用系统无法利用ApacheSOAP将其业务功能拓展到Internet。本文基于ApcheSOAP2.2介绍一种方法扩展它的功能,使它可以集成CORBA对象提供服务。目前提供同样功能的亦有capeclear公司的capeclear、IONA公司的XMLBus等商用产品。本文希望通过一些尝试性的工作,探讨Web服务与CORBA桥接的部分基本问题和实现策略,并以ApacheSOAP为平台给出可行的实现,希望能与对此问题有兴趣的读者交流。
基本问题
SOAP定义了一种使用web协议传递XML格式消息的消息传递系统。如果所传递的XML消息遵循SOAP Request和SOAP Response消息格式,SOAP就表达了一种与CORBA的ORPC相似的交互模型,它们之间是可以自然映射的。下文中我们基于ORPC讨论Web服务与CORBA系统的映射。
我们的目标是:Web服务的开发者可以使用CORBA对象来实现服务功能,这些CORBA对象可能已经存在或有待开发;SOAP客户以标准的方式访问服务,不感知请求的执行目标是CORBA对象或其它编程实体。
为了实现这一目标,需要一个单向的桥接系统来执行SOAP域到IIOP域的请求翻译(包括将IIOP应答转换为SOAP应答)。称其为"单向"是因为它不需要支持位于IIOP域内的CORBA客户访问位于SOAP域内的Web服务。这样的桥接系统中的基本问题包括:
-
访问端点与目标标识映射CORBA使用对象引用唯一地标识一个CORBA对象,它包括该对象的侦听地址和对象标识;而Web服务通常依赖于运行在web container中的一个servlet接收、分发SOAP请求,该servlet的地址是service的访问端点,各service通过局部唯一的ID相区别。因此,桥接系统需要负责将对某个servcie的SOAP请求分发到正确的CORBA对象上,即维护servcieID与对象引用的映射。
-
操作映射这个映射十分简单,一般只需将servcie的方法名映射到CORBA对象的操作名即可。
-
数据类型映射CORBA IDL提供了丰富的数据类型,不仅简单类型分类详细(如表达整形的数据类型就包括6种),而且还定义了特有的复杂类型,如序列、值类型、对象引用等。相比之下,SOAP及XMLSchema规范提供的数据类型要简单得多。为实现SOAP域到CORBA域的请求翻译,需要在部署时刻使用适当的XML Schema定义参数类型;在运行时刻将SOAP请求中的XML数据类型转换为IDL类型,并将IIOP应答消息中的IDL类型映射为XML类型。
下面我们基于Tomcat4.1+ApacheSOAP2.2环境,通过扩展ApaheSOAP来实现一个SOAP2CORBA单向请求级桥接系统,并重点讨论上述第一个问题和第三个问题的解决方案。
总体结构
下图示意ApacheSOAP的主要功能模块。ApacheSOAP的体系结构具有较好的可扩展性,这来主要自于它的可插拨的Provider和可扩展的类型映射表。
Provider是SOAP引擎(即RPCRouterServlet)和服务实现之间的一层抽象,它将所有的服务执行过程抽象为"定位"和"激发"两个方法,以标准的接口连接不同语言、不同机制的服务实现。用户可以跟据后端系统的特征实现自定义的Provider(实现org.apache.soap.utils.Provider接口),其定位和激发过程完全可定制。通过适当的部署,用户自定义的Provider可以运行时刻加载执行服务。部署描述符通过"选项"允许用户在部署时刻传递任何与服务定位和激发有关的信息。
为了实现我们的目标------使用CORBA对象实现Web服务,我们需要一个CORBAProvider(如图中红色部分所示)。CORBA Provider是SOAP引擎与CORBA对象之间的连接器,它的功能是:
- 确定请求目标对象的IOR
- 将SOAP请求转换为CORBA调用
CORBAProvider具有双重身份:从SOAP引擎角度看,它是服务的执行者,SOAP请求在CORBAProvider中执行并返回结果;另一方面,它作为CORBA客户端调用远端的CORBA对象实现来执行服务,所以CORBAProvider之中应包含一个客户端ORB。当然, CORBAProvider另一个隐含的功能是,构造激发CORBA调用所需的CORBA类型参数,并将CORBA调用的返回值转换为SOAP编解码引擎所能识别的Java类型。
参数构造功能除了可以由CORBAProvider提供之外,也可以通过扩展的编解码器来实现。ApacheSOAP的编解码器采用映射表管理类型序列化器和反序列化器,允许部署时刻增加类型映射。这种良好的设计结构允许用户及二次开发者为编解码器作应用级或平台级的扩展,以适应接入新系统及用户定制编解码的要求。本文的下一篇中将详细讨论参数映射问题。
实现CORBAProvider
下面首先介绍一个简单的CORBAProvider的实现。称之为"简单"是因为我们对这个CORBAProvider的功能作了如下的限制:
- 每个服务中只部署一个CORBA对象。这个假设并不算过份,因为ApacheSOAP2.2的每个服务中亦只能部署一个Java对象或EJB;
- 操作参数和返回值只能使用简单类型(即IDL2Java mapping中规定的Basic类型)。后文中我们将讨论对复杂类型的支持。
- 只支持IN参数,不支持INOUT参数和OUT参数。事实上,目前许多商用产品也只支持IN参数。
CORBAProvider需要实现org.apache.soap.utils.Provider接口,它的定义如下所示:
public interface Provider {
public void locate( DeploymentDescriptor dd, Envelope env, Call call,
String methodName, String targetObjectURI,
SOAPContext reqContext) throws SOAPException ;
public void invoke(SOAPContext req, SOAPContext res) throws SOAPException ;
}
|
定位目标对象
首先看看如何确定请求的目标对象,这个功能实现在Provider接口的locate()方法里。Locate()方法提供若干参数,其中最重要的是:此次请求的Call对象(org.apache.soap.rpc.Call)和本服务的部署描述符(org.apache.soap.server.DeploymentDescriptor)对象,它们相互合作来确定请求目标对象。
Call对象封装了一次SOAP请求的信息,包括请求目标服务标识、请求的方法名、请求参数等。客户通过call.setTargetObjectURI("urn:serviceID")设定服务的标识,客户并不了解服务的后端实现机制。因此,所谓"确定请求目标对象"就是将这个对SOAP客户有意义的serviceID通过某种内部机制映射为后端唯一的CORBA对象的IOR。
我们可以直接使用一张哈希表来做这个映射,但一个更贴近实际应用场景、更灵活的方案是使用名字服务(Naming Service)。名字服务是CORBA系统常用的定位机制,它通常运行于一个公知的地址(或有公知的方式拿到它的引用)。CORBA服务器在启动时将CORBA对象的逻辑名注册在名字服务中,CORBA客户通过逻辑名获取对象引用,从而使得对象引用对客户透明。为此,我们使用名字服务来间接地映射serviceID与CORBA对象IOR,即形成逻辑上的<serviceID,logicNameOfCorbaObject, IOR>映射关系。具体的方法是:
- 部署时刻,用户提供CORBA对象所使用的名字服务的地址和它在名字服务中的逻辑名,这些信息将记录在服务的部署描述符中(DeploymentDesciptor);
- 执行时刻,CORBAProvider根据serviceID查找相应的部署描述符对象,得到名字服务和CORBA对象的逻辑名。然后创建名字服务、解析逻辑名获得该对象的IOR。
下面我们用代码说明如何实现CORBAProvider的locate()方法。这里我们只给出主要的代码段,可能不包含必要的变量声明和异常处理,完整的代码请参见参考资料。
步骤一、假设名字服务的地址通过它的引用文件来提供,并且将该引用文件存于指定目录下,引用文件名和注册名在部署时放在"选项"列表中(参见"服务部署与运行"一节)。那么首先需要从服务的部署描述符中获取选项表,并取出引用文件名和注册名:
Hashtable props = dd.getProps();
corbaNamingRef = (String) props.get("CorbaNamingRef");
registeredName = (String) props.get("RegisteredName");
|
步骤二、初始化ORB,从引用文件中反串化名字服务的IOR并创建名字服务的根NamingContex。示例所使用的ORB是OBacus4.1for Java。
try{
java.util.Properties sysprops = System.getProperties();
sysprops.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORB");
sysprops.put("org.omg.CORBA.ORBSingletonClass",
"com.ooc.CORBA.ORBSingleton");
String[] args = new String[1];
args[0] = "";
orb = org.omg.CORBA.ORB.init(args,sysprops) ;
}catch(Exception e ){…… }
NamingContext nc = null;
try {
nc = NamingContextHelper.narrow(obj);
}catch(org.omg.CORBA.BAD_PARAM ex){ ……}
|
步骤三、使用名字服务解析对象的注册名,获得目标对象的对象引用。这里我们获得的targetObject是一个org.omg.CORBA.Object类型的通用对象引用。
NameComponent[] cps = new NameComponent[1];
NameComponent component = new NameComponent(registeredName,"");
cps[0]=component;
try{
targetObject = nc.resolve(cps);
}catch(org.omg.CORBA.UserException e) {
throw new SOAPException(Constants.FAULT_CODE_SERVER,"cannot resolve name");
} |
构造参数与激发请求
确定目标对象IOR后CORBAProvider就可以作为CORBA客户端发起CORBA调用了。激发CORBA调用前,必须从SOAP消息中解码参数,这里我们暂且只处理基本类型。
基本IDL类型可直接映射为相应的Java数据类型,如下表所示。因此使用ApacheSOAP2.2解码器解码得以的Java类型参数可直接用于构造CORBA请求。这种情况下,SOAP客户端和服务器都无需作任何扩充。
IDL2Java基本类型的映射表
|
IDL类型
|
Java类型
| |
IDL类型
|
Java类型
| | boolean | boolean | | Long | int | | char | char | | unsigned | long int | | wchar | char | | Long long | long | | Octet | byte | | unsigned longlong | long | | string | java.lang.String | | Float | float | | wstring | java.lang.String | | double | double | | short | short | | fixed | java.math.BigDecimal | | Unsigned short | short | | | |
与通常的CORBA客户端相同,CORBAProvider可以使用两种方式请求CORBA对象:
一、静态Stub方式
静态方法激活需要CORBAProvider能够获得CORBA对象的stub类,因此需要IDL文件和IDL编译器,或通过别的途径(如动态代码下载)获得stub类。CORBAProvider如何装载正确的stub类有不同的实现途径,其中一种方法是,在部署服务时指定该服务使用的CORBA接口的stub类名和helper的类名。这样,静态方式需要部署时刻供名字服务地址、注册名、stub类名和helper类名4条信息。
下面用代码说明使用静态Stub方式的CORBAProvider的invoke()方法的具体实现。同样的,文中只给出主要代码,完整代码请参见参考资料。
步骤0、为了在invoke()方法中得到StubO类和Helper类,需要在locate()方法的步骤一处增加以下代码。
Hashtable props = dd.getProps();
stubClassName = (String) props.get("StubClassName");
helperClassName = (String )props.get("HelperClassName");
|
步骤一、装载Helper类和Stub类;调用Helper的narrow静态方法将locate方法获得的通用对象引用紧缩为特定类型的对象引用,从而获得Stub对象。这个过程使用Java的反射机制实现。
Class helperClass;
Class stubClass ;
java.lang.Object stub;
try {
helperClass = reqContext.loadClass(helperClassName) ;
stubClass = reqContext.loadClass(stubClassName) ;
Class c[] = new Class[1];
c[0] = stubClass;
Method m = MethodUtils.getMethod(helperClass,"narrow",c) ;
org.omg.CORBA.Object o[] = new org.omg.CORBA.Object[1];
o[0] = targetObject;
stub = m.invoke(helperClass,o) ;
} catch(Exception exp) {
orb.destroy() ;
throw new SOAPException(Constants.FAULT_CODE_SERVER,
"Can't load stub or helper class ",
exp);
} |
步骤二、从Call对象中得到请求参数,构造参数值数组args和参数类型数组argTypes。这个过程与ApacheSOAP的RPCRouter.java中相应的代码相同,此处不赘述。
步骤三、在Stub对象上调用所请求的方法,这个过程同样使用Java的反射机制实现:首先使用Stub对象、方法名和参数类型数组构造Method对象,然后激发该Method对象。方法调用的结果封装在Bean对象中。
Method m = null;
Parameter ret = null;
Bean result = null;
try {
m = MethodUtils.getMethod (stub, call.getMethodName(), argTypes);
} catch (NoSuchMethodException e) { ……}
try {
result = new Bean (m.getReturnType (), m.invoke (stub, args));
if (result.type != void.class) {
ret = new Parameter (RPCConstants.ELEM_RETURN,
result.type, result.value, null);
}
} catch (InvocationTargetException e) {……}
|
步骤四、创建Response对象,将其封装在信封内编码送回客户。TemplateProvider.java中有此段代码,此处亦略去。
二、动态DII方式
动态方式不需要任何类型特定的信息,也不需要IDL文件或编译器,能以较简单的方式构成一个通用的CORBA客户端。由于不需要将通用对象引用紧缩为特定类型的CORBA对象,所以部署描述符无需提供stub类名和helper类名。
但是,正是因为没有特定类型的Stub类,DII客户端必须自已处理参数和返回值的编解码。因此,DII客户端需要了解每个参数及返回值的类型,才能调用正确的Any.insert_xxx()方法向参数列表中插入参数,才能正确设置返回值的类型码,才能按正确类型从返回值的Any对象中抽取返回值。也就是说,动态DII方式的CORBAProvider需要依赖额外的某种机制提供参数及返回值的类型信息。一个办法是使用接口池(Interface Repository),CORBAProvider向接口池查询每个操作参数和返回值类型码来确定如何处理它们。
这种方式较静态方式稍复杂,此处不做详细的介绍,如果你有兴趣可以试试看。
服务部署与运行
前文提到,CORBAProvider定位CORBA对象时需要名字服务地址和CORBA对象在名字服务中的逻辑名,如果使用静态方法激发则还需要stub类名和helper类名,这些信息由用户(服务提供者)在部署时提供。
如果使用ApacheSOAP的web部署界面,需将这个服务部署为USER_DEFINED类型,并指定CORBAProvider的类名(如,cal.CORBAProvider)。名字服务地址和CORBA对象的逻辑名等信息通过选项(Options)传递给部署描述符,选项的示例如下:
|
key
|
value
| | NamingServiceRefFileName | ns.ior | | RegisteredName | calculator | | StubClassName | cal._CalculatorStub | | HelperClassName | cal.CalculatorHelper |
如果使用ApacheSOAP的命令行部署工具(即serviceManagerClient),部署描述信息的XML格式如下所示:
<isd:service xmlns:isd=http://xml.apache.org/xml-soap/deployment
id="urn:calculator">
<isd:provider type="cal.CORBAProvider" scope="Application"
methods="add">
<isd:option key=" NamingServiceRefFileName " value="ns.ior"/>
<isd:option key=" RegisteredName " value="calculator" />
<isd:option key=" StubClassName " value=" cal._CalculatorStub " />
<isd:option key=" HelperClassName " value=" cal.CalculatorHelper " />
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service> |
完成CORBAProvider的编写之后,就可以按如下步骤使用它来部署和访问CORBA对象了。这里tomcat和ApacheSOAP都使用的是缺省配置。
- 请先检查系统中是否安装了一个Java的ORB,并保证ORB和NamingService的库在系统的CLASSPATH中;
- 运行名字服务器和CORBA服务器程序,将名字服务器的对象引用保存到文件中;
- 将名字引用文件复制到<tomcat-dir>/webapps/soap/目录下;
- 按照上文所述部署服务,需注意的是,CORBAProvider的class文件应置于<tomcat-dir>/webapps/soap/WEB-INF/classes目录下或系统CLASSPATH下;使用静态激发方法时IDL编译器产生的stub文件也应置于该目录下;
- 编写相应的客户端程序,具体方法请参见ApacheSOAP的文档;
- 启动tomcat,执行客户端。
至此之止,我们介绍了如何为ApacheSOAP编写一个CORBAProvider来访问CORBA对象,它的基本原理是:使用名字服务定位对象,使用静态或动态方式激发CORBA调用,使用部署描述符提供必要信息。但是我们做了一个重要的简化:调用参数和返回值只使用简单类型 。在本文的下一篇中,我们将主要针对如何为CORBAProvider增加对复杂类型的支持而进行讨论,并将考虑在服务中部署多接口等更具有挑战意义的问题。
参考资料
关于作者
对本文的评价
|