引言
本文描述将原型组合应用程序从 WebSphere Application Developer-IE v5.1 迁移到 WebSphere Integration Developer v6 的一些问题和解决方案。我们将介绍的主要问题包括 WSDL 绑定、WSDL 接口、XSD 定义和业务流程执行语言(Business Process Execution Language,BPEL)编码。尽管 WebSphere Integration Developer v6 具有功能强大的迁移向导,但我们发现可以对简单项目进行自动迁移。然而,更复杂的 BPEL 应用程序将需要对迁移过程的更深入了解。有关详尽的迁移注意事项,请参考 WebSphere Integration Developer 帮助或 WebSphere Integration Developer /WPS 信息中心。
绑定的迁移问题
本系列中的第一篇文章确定了一些由一家银行客户发起的用例。“贷款申请”用例调用了一个 BPEL 业务流程。该业务流程完成一系列调用服务来处理贷款的步骤。所调用的有些服务使用了 Java™ 或 EJB 绑定。
Java 绑定问题
WebSphere Integration Developer v6 不支持 WebSphere Application Developer-IE 所生成的 WSDL 中使用的原始 Java 绑定类型。服务/端口定义使用了某种 Java 类型。因此在 WSDL 中生成了一个 Java ClassName 而不是一个端点地址。(请参见清单 1)。
清单 1. 带 Java 绑定的 WSDL
<
service
name
="LoanTrackingServiceProxyPortTypeService"
>
<
port
binding
="tns:LoanTrackingServiceProxyPortTypeJavaBinding"
name
="LoanTrackingServiceProxyPortTypeJavaPort"
>
<
java:address
className
="loantrackingservice.LoanTrackingServiceProxy"
/>
</
port
>
</
service
>
如果将此类 WSDL 直接导入 SCA 模块,那么即使没有异常或由 WebSphere Integration Developer 标记的错误,所生成的导入也无法成功进行绑定(请参见图 1)。事实上,WebSphere Integration Developer V6 仅支持带 SOAP 绑定的 Web 服务导入。因此,导入的端点将保留为空,从而在将模块部署到 WPS 并在我们尝试调用它时导致运行时异常。
图 1. 带 Java 绑定的 WSDL 导入
Enterprise JavaBean (EJB) 绑定问题
WebSphere Integration Developer v6 不支持 WebSphere Application Developer-IE 所生成的 WSDL 中使用的原始 EJB 绑定类型。其中的服务/端口定义(类似于前面提到的 Java 类型)使用了 EJB 类型。因此,提供了 EJB Home ClassName 和 JNDI Name 而不是端点地址。(请参见清单 2)。
清单 2. 带 EJB 绑定的 WSDL
<
service
name
="LoanTrackingService"
>
<
port
binding
="binding1:LoanTrackingServiceEJBBinding"
name
="LoanTrackingServiceEJBPort"
>
<
ejb:address
className
="loantrackingservice.LoanTrackingServiceHome"
jndiName
="ejb/loantrackingservice/LoanTrackingServiceHome"
/>
</
port
>
</
service
>
这也会由于前面提到的相同原因而在执行期间导致异常(请参见图 2)。
图 2. 带 EJB 绑定的 WSDL 导入
解决方案
- 丢弃带 EJB 或 Java 绑定的以前 WSDL。
- 重新生成带 SOAP 绑定的对应 Web 服务,并将其直接导入 SCA 模块。
- 修改 BPEL 流程以引用新导入的 Web 服务接口。
WSDL 接口的迁移问题
本系列第一部分中讨论过的用例之一是贷款发放场景。在客户申请贷款之后,一名扮演“平台销售 (Platform Sales)”角色的银行员工审核贷款请求并批准或拒绝该请求。此用例的部分地方调用了一个信用记录服务。在此服务调用期间,使用 document/literal 样式并避免 RPC 样式及其 soapenc:Array
是非常重要的。但是在需要连接到某个使用该样式的服务的场景中,我们如何做呢?接下来的部分将讨论此问题以及避免使用重复命名空间的重要性。
避免使用 soapenc:Array
避免创建或导入引用 soapenc:Array
类型的 WSDL 接口,因为 SCA 编程模型本身并不支持该接口。WPS 6.0 将 RPC 样式的 SOAP 编码数组类型视为具体类型的无限序列。因此,建议不要使用它们。标准方法是使用 document/literal 样式。WebSphere Application Developer-IE 生成的原始 WSDL 文件中的 soapenc:Array
类型定义如清单 3 所示。
清单 3. soapenc:Array 的 XML 类型定义
<
complexType
name
="ArrayOfLoanRequestBean"
>
<
complexContent
>
<
restriction
base
="soapenc:Array"
>
<
all
/>
<
attribute
ref
="soapenc:arrayType"
wsdl:arrayType
="xsd1:LoanRequestBean[]"
/>
</
restriction
>
</
complexContent
>
</
complexType
>
解决方案 1
- 更改 WSDL 中的数组类型定义。
- 重新生成 Web 服务。
- 再次运行 WebSphere Application Developer-IE Service Project 迁移向导,以在 WebSphere Integration Developer 中生成新构件(服务项目和 BEPL 流程中修改后的 Java 代码片段)。
解决方案 2
然而,在某些情况下,SCA 应用程序必须调用某个使用 soapenc:Arraytype
的外部服务。(例如,调用某个外部信用记录服务,该服务在其接口定义中使用了 soapenc:Array
类型)。在这种情况下,修改服务实现和接口定义是不可能的,因此必须使用其他解决方案来解决此问题。
- 通过添加占位符元素来人工更改导入的外部 WSDL,这些元素可以在将该 WSDL 导入 SCA 模块后促进后续的迁移工作。
- 使用服务数据对象(Service Data Object,SDO)API 来处理SCA 模块中的 Java 代码片段中的
soapenc:Array
数据。
清单 4 是一个示例,其中的数据对象根类型为 soapenc:Array
。请注意 sampleElements
DataObject 是如何通过所列出的第二个模式来创建的。首先获得该 DataObject 的类型,然后再获得 sampleStructElement
的属性(请参见清单 5)。这实际上是一个占位符属性,并且仅用于获得一个有效的属性来将 DataObjects 添加到该序列。
清单 4. 示例 WSDL 代码
<
s:schema
elementFormDefault
="qualified"
targetNamespace
="http://soapinterop.org/xsd"
>
<
s:import
namespace
="http://schemas.xmlsoap.org/soap/encoding/"
/>
<
s:import
namespace
="http://schemas.xmlsoap.org/wsdl/"
/>
<
s:complexType
name
="SOAPStruct"
>
<
s:sequence
>
<
s:element
minOccurs
="1"
maxOccurs
="1"
form
="unqualified"
name
="varInt"
type
="s:int"
/>
<
s:element
minOccurs
="1"
maxOccurs
="1"
form
="unqualified"
name
="varString"
type
="s:string"
/>
<
s:element
minOccurs
="1"
maxOccurs
="1"
form
="unqualified"
name
="varFloat"
type
="s:float"
/>
</
s:sequence
>
</
s:complexType
>
<
s:complexType
name
="ArrayOfSOAPStruct"
>
<
s:complexContent
mixed
="false"
>
<
s:restriction
base
="soapenc:Array"
>
<
s:attribute
wsdl:arrayType
="s0:SOAPStruct[]"
ref
="soapenc:arrayType"
/>
</
s:restriction
>
</
s:complexContent
>
</
s:complexType
>
</
s:schema
>

<
wsdl:message
name
="echoStructArraySoapIn"
>
<
wsdl:part
name
="inputStructArray"
type
="s0:ArrayOfSOAPStruct"
/>
</
wsdl:message
>
<
wsdl:message
name
="echoStructArraySoapOut"
>
<
wsdl:part
name
="return"
type
="s0:ArrayOfSOAPStruct"
/>
</
wsdl:message
>

<
wsdl:operation
name
="echoStructArray"
>
<
wsdl:input
message
="tns:echoStructArraySoapIn"
/>
<
wsdl:output
message
="tns:echoStructArraySoapOut"
/>
</
wsdl:operation
>


SampleElements.xsd

<
schema
targetNamespace
="http://sample/elements"
xmlns
="http://www.w3.org/2001/XMLSchema"
xmlns:tns
="http://sample/elements"
>
<
element
name
="sampleStringElement"
type
="string"
/>
<
element
name
="sampleStructElement"
type
="any"
/>
</
schema
>
清单 5. 该 Web 服务的示例客户端代码
//
Create the input DataObject and get the SDO sequence for the any element
DataFactory dataFactory
=
DataFactory.INSTANCE;
DataObject arrayOfStruct
=
dataFactory.create(
"
http://soapinterop.org/xsd
"
,
"
ArrayOfSOAPStruct
"
);
Sequence sequence
=
arrayOfStruct.getSequence(
"
any
"
);

//
Get the SDO property for sample element we want to use here to populate the sequence
//
We have defined this element in an XSD file, see SampleElements.xsd
DataObject sampleElements
=
dataFactory.create(
"
http://sample/elements
"
,
"
DocumentRoot
"
);
Property property
=
sampleElements.getType().getProperty(
"
sampleStructElement
"
);

//
Add the elements to the sequence
DataObject item
=
dataFactory.create(
"
http://soapinterop.org/xsd
"
,
"
SOAPStruct
"
);
item.setInt(
"
varInt
"
,
1
);
item.setString(
"
varString
"
,
"
Hello
"
);
item.setFloat(
"
varFloat
"
,
1.0f
);
sequence.add(property, item);
item
=
dataFactory.create(
"
http://soapinterop.org/xsd
"
,
"
SOAPStruct
"
);
item.setInt(
"
varInt
"
,
2
);
item.setString(
"
varString
"
,
"
World
"
);
item.setFloat(
"
varFloat
"
,
2.0f
);
sequence.add(property, item);

//
Invoke the echoStructArray operation
System.out.println(
"
[client] invoking echoStructArray operation
"
);
DataObject echoArrayOfStruct
=
(DataObject)interopTest.invoke(
"
echoStructArray
"
, arrayOfStruct);

//
Display the results
if
(echoArrayOfStruct
!=
null
)

...
{
sequence=echoArrayOfStruct.getSequence("any");
for (int i=0, n=sequence.size(); i<n; i++)

...{
item=(DataObject)sequence.getValue(i);
// Create the input DataObject and get the SDO sequence for the any element
DataFactory dataFactory=DataFactory.INSTANCE;
DataObject arrayOfStruct =
dataFactory.create("http://soapinterop.org/xsd","ArrayOfSOAPStruct");
Sequence sequence=arrayOfStruct.getSequence("any");

// Get the SDO property for sample element we want to use here to populate the sequence
// We have defined this element in an XSD file, see SampleElements.xsd
DataObject sampleElements=dataFactory.create("http://sample/elements",
"DocumentRoot");
Property property = sampleElements.getType().getProperty("sampleStructElement");

//Add the elements to the sequence
DataObject item=dataFactory.create("http://soapinterop.org/xsd", "SOAPStruct");
item.setInt("varInt", 1);
item.setString("varString", "Hello");
item.setFloat("varFloat", 1.0f);
System.out.println("[client] item varInt = "+ item.getInt("varInt")+"
varString="+item.getString("varString")+" varFloat="+item.getFloat("varFloat"));
}
XSD 迁移问题
在前面提到的与贷款发放有关的同一个用例中,我们使用了返回类型 Object
,此类型生成一个 anyType
类型,然而后者却具有下一部分所描述的后果。
避免使用 xsd:anyType
准确定义 WSDL 接口。避免使用引用 xsd:anyType
的 XSD complexType,因为WebSphere Integration Developer v6 不支持该类型。在尝试设置该类型的值时将会导致错误。
在下面的示例中,导入 SCA 模块的 WSDL 有一个指定了 anyType
的 complexType,从而在操作的输出中导致了类型 anyType
(请参见图 3)。虽然在开发期间没有在 WebSphere Integration Developer 中检测到异常或错误,但是在运行时期间,当尝试获取或设置 Java 代码片段中的业务对象数据项的值时,将会引发一个“类强制转换异常”。
图 3. 带 xsd:anyType 的 XML 类型定义
例如,在尝试使用一个 Boolean 值来设置 Java 代码片段中的 getVariableReturn
时,将会导致从 WebSphere Process Server 运行时中引发类强制转换异常。
图 4. anyType 导致的类强制转换异常
//将存在一个由类型为 anyType
的 value
项导致的类强制转换错误; setDecisionVar.set("getVariableReturn",(Object)(new Boolean(true));
解决方案
- 修改对应的会话 Bean 接口,以避免使用
Object
作为输入/输出参数类型。(通过将参数类型指定为 Object
,所生成的 WSDL 将指定 xsd:anyType
作为对应的 XML 元素类型。) - 重新生成 Web 服务,随后可将其导入 SCA 模块。
- 按照新的 WSDL 修改迁移后的 BPEL 流程中的所有相关调用点。
BPEL 迁移问题
本系列第一部分中使用的贷款发放用例使用一个信用记录服务来批准或拒绝贷款。其中存在动态设置该服务端点的要求。该端点最初是使用业务流程引擎(Business Process Engine,BPE)API 来设置的。然而,WebSphere Integration Developer /WebSphere Process Server 具有一种更好的机制可供使用。
动态设置端点(第一个问题)
WebSphere Application Developer-IE 可以使用 BPEL 流程中嵌入的 Java 代码片段来动态设置端点。然而,该代码不再有效,因为 WebSphere Process Server 中的 BPE API 已经更改。因此,需要更新代码以使用新的 API。用于动态设置端点的原始代码如清单 8 所示。在清单 9 中,该代码已迁移为使用新的 WebSphere Process Server BPE API。
清单 8. WebSphere Application Developer-IE 中的动态端点设置代码
String ep
=
getApproveCheckRequest().getApprovalExtensionEP();
com.ibm.websphere.srm.bpel.wsaddressing.AttributedURI address
=
new
AttributedURI();
com.ibm.ws.webservices.engine.types.URI addressValue
=
new
URI(ep);
com.ibm.websphere.srm.bpel.wsaddressing.EndpointReferenceType
e
=
new
EndpointReferenceType();
address.setValue(addressValue);
e.setAddress(address);
this
.setPartnerLinkLoanApprovalExtension(e);
清单 9. WebSphere Integration Developer 中的动态端点设置代码
//
Get the new address of approvalExtension parnter
String ep
=
approveCheckRequest.getString(
"
approvalExtensionEP
"
);
System.out.println(
"
Setting approval extension endpoint to:
"
+
ep);

if
(ep
!=
null
&&
ep.length()
>
0
)
...
{
ServiceManager sm = ServiceManager.INSTANCE;
Service s = (Service)sm.locateService("LoanApprovalExtension");

EndpointReference endPoint = s.getEndpointReference();
//Update the endpoint address for the partner "LoanApprovalExtension"
endPoint.setAddress(ep);
}
尽管用于设置引用伙伴端点的代码已经更新,但是如果该伙伴已连接到某个组件(如果在运行时期间在动态重置之前向它分配了绑定),则端点更改将不会生效。在运行时期间,调用仍将定向到缺省端点。
动态设置端点(第二个问题)
当将 SCA 模块中的某个引用保留为空时(例如,它未连接到任何 SCA 组件),则可以使用上述动态端点设置代码来将绑定信息附加到该空引用。然而,如果我们尝试使用不同的端点绑定来再次更新其端点,则该引用将不再有效,并且会引发一个“文档根目录错误”异常。这是 WebSphere Process Server v6 中的一个限制。
解决方案
SCA 编程模型可以使用 WebSphere Integration Developer /WebSphere Process Server“选择器”机制在运行时动态更改端点。选择器是 WebSphere Integration Developer /WebSphere Process Server 支持的一种 SCA 组件,可用于将来自客户端应用程序的操作路由到多个可能的 SCA 服务实现之一。因此,从功能的角度看,它可以完成与通过 BPE API 来动态设置端点相同的事情。图 5 显示了带有选择器的组装关系图
图 5. 使用选择器

结束语
本文描述了在将 WebSphere Application Developer-IE 业务集成项目迁移到 WebSphere Integration Developer 业务集成模块期间遇到的一些问题,并提供了一些解决方案。我们介绍了将 Java 和 EJB 绑定替换为 SOAP 绑定的重要性;我们不提倡使用 soapenc:Array
,并提供了一种与使用 soapenc:Array
的服务进行交互的方法,用于那些无法更改的服务的场景(例如,第三方服务)。我们考虑了避免在 WSDL 定义中使用重复命名空间的重要性和避免使用 XSD:anyType
定义的原因。最后,我们考虑了使用选择器来动态更改端点而不是使用 BPE API。
参考资料
学习
避免使用重复命名空间
在 WebSphere Integration Developer 中,您不能导入两个带有相同命名空间的不同 WSDL 文件。当在完全相同的命名空间中声明了复杂类型(在 WSDL 或 XSD 中)时,则不能通过迁移向导过程来正确迁移它们。在 IBM WebSphere® Process Server v6 中,具有相同名称和目标命名空间的两个不同 WSDL/XSD 定义是不允许的。
清单 6 和 7 显示了两个具有相同命名空间的 WSDL 定义。
清单 6. LoanRequest1.WSDL
<
wsdl:definitions
xmlns:tns
="http://loanrequest/LoanRequest"
......
targetNamespace
="http://loanrequest/LoanRequest"
>

<
message
name
="processLoanRequest"
>
<
part
name
="loanAmount"
type
="xsd:decimal"
></
part
>
<
part
name
="customerId"
type
="xsd:string"
></
part
>
<
part
name
="bankId"
type
="xsd:string"
></
part
>
<
part
name
="ssn"
type
="xsd:string"
></
part
>
<
part
name
="processConfig"
type
="tns:ProcessConfig"
></
part
>
<
part
name
="productId"
type
="tns:ProductId"
></
part
>
</
message
>
清单 7. LoanRequest2.WSDL
<
wsdl:definitions
xmlns:tns
="http://loanrequest/LoanRequest"
......
targetNamespace
="http://loanrequest/LoanRequest"
>

<
message
name
="processLoanRequest"
>
<
part
name
="loanAmount"
type
="xsd:decimal"
/>
<
part
name
="customerId"
type
="xsd:string"
/>
<
part
name
="bankId"
type
="xsd:string"
/>
<
part
name
="ssn"
type
="xsd:string"
/>
<
part
name
="configParams"
type
="tns:ConfigParams"
/>
</
message
>
解决方案
- 更该命名空间以使它们成为唯一的。
- 验证命名空间更改没有导致副作用。清除工作区并重新构建所有项目。如果没有在 WebSphere Integration Developer 中检测到任何错误,则将更新后的 SCA 应用程序部署到 WebSphere Process Server 中并进行测试。
对于从 EJB 生成的任何 WSDL 和 XSD,我们应该确保目标命名空间是唯一的(Java 类名称和包名称用于创建目标命名空间),以避免在迁移到 WebSphere Process Server V6 时发生冲突。