 | 级别: 初级 Martin Smolny (Martin.Smolny@de.ibm.com), WebSphere Process Choreographer 开发人员, IBM Boeblingen,德国
2004 年 9 月 01 日 本文描述了通过使用 Extended SQL(ESQL)来扩展 WebSphere
®Business Integration Message Broker V5 和 WebSphere MQ Integrator Broker V2.1 功能的新方法。通过对一个带有大量代码的简单案例研究来实现一个插件解析器并且把跟踪调用增加到 ESQL 代码中。
ESQL--简短概述
WebSphere MQ Integrator Broker V2.1 和 WebSphere Business Integration Message Broker V5 提供了扩展 SQL(ESQL),一种适合消息和数据库内容操纵的强大的语言。该语言起源于 SQL Version 3。和 SQL 不同,ESQL 程序可以包含许多按照编写顺序执行的语句。它也支持像 IF-conditions 和 WHILE-loops 一样的语言元素,并且允许实现复杂的处理过程。消息处理比较简单且实现的代码在所有被支持的平台上可移植。
在新版本的 WebSphere Business Integration Message Broker V5 中,ESQL 提供了一些新的语言元素,这些元素允许在所谓的过程和函数中实现通用的功能。虽然这是主要改进的地方,但是在 ESQL 中仅仅能执行过程或函数而不是其它语言。
因为示例是使用 WebSphere MQ Integrator Broker 开发的,所以本文涉及到了 WebSphere MQ Integrator Broker V2.1,但是本文中的信息同样适合 WebSphere MQ Integration Message Broker V5。
扩展的 WebSphere MQ Integrator Broker
为了在 ESQL 限制之外扩展 WebSphere MQ Integrator Broker 的功能,可以用 C 或 Java
™ 实现插件节点。虽然插件节点组成了可以实现令人感兴趣功能的强大方法,但是该方法可以导致更复杂的处理过程:
设想一个公司拥有基于服务器的用户姓名地址录,使用该用户姓名地址录检查 e-mail 地址的有效性。程序员必须实现一个这样流程,该流程处理清单 1 中的消息并且检查该姓名地址录中存在的所有收件人的 e-mail 地址。
清单 1. 待处理的示例消息
<CompanyNewsletter>
<Recipients>
<Recipient>john@doe.com</Recipient>
<Recipient>jane@doe.com</Recipient>
...
</Recipients>
<Text>
...
</Text>
</CompanyNewsletter>
|
该公司有能够检查该姓名地址录中的 e-mail 地址的插件节点。如果使用该组件,那么实现处理过程的唯一方法是使用计算节点。在 ESQL 中包含了一个循环,该循环为每个 e-mail 地址创建一个请求消息并且把该请求信息广播到插件节点(参见清单 2)。如果没有异常出现,也就是插件节点的处理过程是成功的,ESQL 代码将继续执行下去。
清单 2. 使用 ESQL 和插件节点的解决方案
WHILE condition
build request message to check for e-Mail-address
PROPAGATE
END WHILE;
send newsletter
|
如果在 ESQL 循环内具有检查 e-mail 地址的可能性(这样更合适),那么就不需要为每个收件人创建一个消息结构。创建这类消息会花费一些编码工作量并且在执行时速度比较慢。
以下的案例研究显示了一个使用 C 实现功能且通过 ESQL 可以访问该功能的完美方法。正如本文的主要焦点是实现插件解析器,本文通过一个简单案例研究来演示它。
案例研究
对于专业的软件开发人员来讲,在客户环境中当产品运行时使用跟踪功能是非常普遍的。对于 ESQL 程序,WebSphere MQ Integrator Broker 可以设置 User Trace 为调试级别。该级别为在 ESQL 中发生的所有代码执行产生非常详细的跟踪。正常级别一点也不显示 ESQL 的执行。本案例研究实现了一个跟踪功能,该功能在正常级别下也能工作。实现允许程序员根据自己的需要控制跟踪粒度。
为了演示跟踪的用法,图 1 显示了一个简单的流程,该流程接受一条输入消息,提取元素 Net 和 Tax,并且计算元素并得到总额。把结果放到消息中,并且传到输出队列中。
图 1. 使用 ESQL 代码的流程
SET OutputRoot = InputRoot;
-- Enter SQL below this line. SQL above this line might be
regenerated, causing any modifications to be lost.
DECLARE pRefNet REFERENCE TO OutputRoot.XML.Bill.Net;
DECLARE pRefTax REFERENCE TO OutputRoot.XML.Bill.Tax;
DECLARE pRefGross REFERENCE TO OutputRoot.XML.Bill.Gross;
SET pRefGross = CAST(pRefNet AS DECIMAL) *
CAST(pRefTax AS DECIMAL) +
CAST(pRetNet AS DECIMAL);
|
本案例研究的目标是为图 1 的 ESQL 代码增加一些跟踪调用。使用提供的 C 函数 cciLog() 把这些跟踪调用编写为 WebSphere MQ Integrator Broker User Trace。为了达到这个目标,实现了一个插件解析器。
插件解析器 CciLogParser
WebSphere MQ Integrator Broker 提供了实现插件解析器的 API。打算使用这些解析器来读取 WebSphere MQ Integrator Broker 中已有内嵌解析器所不能访问的以及不能转化为逻辑消息树的消息表示。如果您对超出本文范围的有关插件解析器的更多信息有兴趣,您可以查阅
WebSphere MQ Integrator Broker 用户编程指南。
The CciLogParser 是用 C 而不是 Java 实现的,因为虽然可以使用 Java 实现插件节点,但是不支持使用 Java 实现插件解析器。每个插件解析器都必须提供名为 bipGetParserFactory() 的函数(参见清单 3)。该函数定义了解析器工厂
ComIbmSampleCciLogParserFactory 和解析器类
CCILOG 。两个名称在代理环境中必须是唯一的。
清单 3.
bipGetParserFactory() 的实现
void LilFactoryExportPrefix * LilFactoryExportSuffix bipGetParserFactory(void) {
CciFactory* factoryObject;
int rc;
static CPI_VFT vftable = {CPI_VFT_DEFAULT};
// initialize the constants used by this plug-in
initParserConstants();
// setup function table with pointers to parser implementation functions
vftable.iFpParseBufferEncoded = cpiParseAndWriteBufferEncoded;
vftable.iFpWriteBufferEncoded = cpiParseAndWriteBufferEncoded;
vftable.iFpSetElementValue = cpiSetElementValue;
vftable.iFpElementValue = cpiElementValue;
// mandatory functions, otherwise WMQI does not use this parser
vftable.iFpCreateContext = cpiCreateContext;
vftable.iFpParseFirstChild = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParseLastChild = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParsePreviousSibling = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParseNextSibling = cpiParseFirstLastChildAndPreviousNextSibling;
// create the parser factory for this plugin
factoryObject = cpiCreateParserFactory(&rc, constParserFactory);
if (factoryObject) {
// Define the classes of message supported by the factory
cpiDefineParserClass(&rc, factoryObject, constParserClass, &vftable);
} else {
static char* errMsg = "CciLogParser.lil: error creating"
" the parser factory " CONST_PARSER_FACTORY;
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "bipGetParserFactory()",
constWBIMBCatalog, 3001, errMsg, errMsg, CONST_VIRTUALTRACENODENAME, 0);
}
return(factoryObject);
}
|
该插件解析器的实现函数的大部分是有一个简单的返回语句组成的。为了访问 ESQL 传递过来的值,该解析器实现了
cpiSetElementValue() 和它相对应的
cpiElementValue() ,后者是允许为 WebSphere MQ Integrator Broker 返回值所必须的。参照 WebSphere MQ Integrator Broker V2.1 自带的示例插件解析器,这两个函数的实现如下:
清单 4.
cpiSetElementValue() 的实现
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
void cpiSetElementValue(CciParser* parser, CciElement* element, CciElementValue* value) {
CciElement* valueElement;
CciSize bufferSize;
int rc;
CciChar buffer[1024]; // maximum supported length of message
char charBuffer[1024];
// set the value
valueElement = element;
if ((cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_VALUE) ||
(cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME_VALUE)) {
cpiSetElementValueValue(&rc, element, value);
} else if (cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME) {
/* Create a new value element, add as a first child, and set the value */
valueElement = cpiCreateElement(&rc, parser);
cpiSetElementType(&rc, valueElement, CCI_ELEMENT_TYPE_VALUE);
cpiSetElementValueValue(&rc, valueElement, value);
cpiAddAsFirstChild(&rc, element, valueElement);
}
buffer[0] = 0;
bufferSize = cpiElementCharacterValue(&rc, valueElement, buffer, 1023);
if (rc==CCI_EXCEPTION) {
cciRethrowLastException(&rc);
} else if (rc==CCI_SUCCESS) {
cciUcsToMbs(&rc, buffer, charBuffer, 1023, BIP_DEF_COMP_CCSID);
if (rc!=CCI_SUCCESS) {
// Error occured, e.g the message contained non-ASCII
// characters which were expanded to more than one byte per character.
sprintf(charBuffer, "CciLogParser.lil: error converting the log text "
"into a C-string, error code %i", rc);
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
} else {
// the original intend of this parser; write the message to the WMQI log
cciLog(&rc, CCI_LOG_INFORMATION, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
}
} else if (rc==CCI_BUFFER_TOO_SMALL) {
// here we could allocate a new buffer and retrieve the text again,
// but this sample just writes an error to the log
strcpy(charBuffer, "CciLogParser.lil: buffer too small for conversion, "
"pass only ASCII style messages");
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
} else {
// unhandled error
sprintf(charBuffer, "CciLogParser.lil: unknown error rc=%i while retrieving the text", rc);
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
}
return;
}
|
cpiElementValue() 的实现和示例的实现是一样的,因此这里不再重复。
如清单 4 所示,在完成通常的处理(在第一个 IF 语句中把被传递的值增加到消息树)之后,该实现使用 WMQI 函数
cpiElementCharacterValue() 访问被传递的元素字符串。除了一些错误检查外,这里需要处理的仅仅是把 Unicode 文本转换成由平台决定的字符串表示并且把文本传递给 WMQI 函数
cciLog() (在清单 5 中显著的粗体)。不巧的是,
cciLog() 期望得到消息源和消息编号。在 Windows 中,该消息源是包含这些消息的动态链接库(DLL),在 UNIX 系统中,该消息源是 XPG4 目录。另外,还必须为 WebSphere MQ Integrator Broker 提供纯文本格式的消息,这允许 WebSphere MQ Integrator Broker 命令
mqsiformatlog 把日志条目转化为易读条目。
因为使用
cciLog() 不是本文的重点,因此我简单地滥用了 WMQI 消息 id 3001。要在生产环境中使用该插件解析器,那么它的实现至少还需要如下的扩展:
- 提供包含所需消息 id 的自己的消息目录。
- 提供三个消息元素,用于每个可能级别(
CCI_LOG_ERROR 、
CCI_LOG_WARNING 和
CCI_LOG_INFORMATION )。
- 处理超过 1023 字符的字符串。
在 ESQL 中使用插件解析器的功能
为了访问插件解析器的功能,必须使用解析器类名。选择“CCILOG”,因此 ESQL 路径
OutputRoot.CCILOG 调用解析器函数,例如,
SET OutputRoot.CCILOG.Msg="a test log entry" 把文本 "a test log entry" 传递给解析器。
清单 5. 在图 1 中最初介绍的原始 ESQL 程序的改进实现。
SET OutputRoot = InputRoot;
-- Enter SQL below this line. SQL above this line might be regenerated, causing any
modifications to be lost.
DECLARE pRefNet REFERENCE TO OutputRoot.XML.Bill.Net;
IF (pRefNet IS NULL) THEN
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Net value was NULL';
ELSE
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Net value was >' || pRefNet || '<';
END IF;
DECLARE pRefTax REFERENCE TO OutputRoot.XML.Bill.Tax;
IF (pRefTax IS NULL) THEN
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Tax value was NULL';
ELSE
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Tax value was >' || pRefTax || '<';
END IF;
DECLARE pRefGross REFERENCE TO OutputRoot.XML.Bill.Gross;
IF (pRefGross IS NULL) THEN
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Gross value was NULL';
ELSE
SET OutputRoot.CCILOG.Msg = 'OutputRoot.XML.Bill.Gross value was >' || pRefGross || '<';
END IF;
SET pRefGross = CAST(pRefNet AS DECIMAL) *
CAST(pRefTax AS DECIMAL) +
CAST(pRefNet AS DECIMAL);
SET OutputRoot.CCILOG.Msg = 'New value for OutputRoot.XML.Bill.Gross >' || pRefGross || '<';
-- Clean Up the CCILOG message tree
DETACH OutputRoot.CCILOG;
|
观察到在 ESQL 程序的结尾,应该把消息树
OutputRoot.CCILOG 分开。因为 CciLogParser 没有实现可运行的函数来序列化消息树,所以 WebSphere MQ Integrator Broker 不会把消息写入到输出的 WebSphere MQ Queue。用于把跟踪文本传递给插件解析器的子元素 Msg 可以任意地选择,也可以使用其它任意的元素名称。
读取跟踪条目
访问跟踪条目有两种方法:
- WMQI User Trace:设置 User Trace 的级别为正常或调试,例如,
mqsichangetrace BROKER -e default [-r] -l normal
使用 mqsireadlog 和 mqsiformatlog 取回条目,例如,
mqsireadlog BROKER -e default -o tempfile.xml
mqsiformatlog -i tempfile.xml
忽略所有 “UserTrace” 类型的条目,寻找 “Information” 和 “Error” 类型的条目。
- Syslog(Windows:Event Viewer):跟踪条目经常被写入 syslog。
所有的条目使用消息 id 3001,在消息文本中节点名称是 “CciLogParser - Extension for ESQL”。
Reflection
回顾最初的示例,可以使用插件解析器来实现基于目录的 e-mail 检查。使用如下语句可以在 ESQL 中检查 e-mail 地址:
...
SET OutputRoot.EMAILCHK.eMail = currentEMailAddress;
IF (OutputRoot.EMAILCHK.IsValid = FALSE) THEN
do some corrective code
END IF;
...
...
该插件解析器在它实现的函数
cpiSetElementValue() 里获得 e-mail 地址,使用姓名地址录服务器检查被传递的值并且设置
IsValid 元素为
TRUE 或
FALSE 。虽然该处理比我们的简单案例研究要成熟的多,但是它们的观念是相同的。
结束语
本文描述了在 WebSphere MQ Integrator Broker 中实现额外功能的新方法。为此,使用一个简单案例研究通过实现插件解析器来演示开发功能的一般概念。
然而,本文描述的方法并不是插件解析器方法最初使用的目的。用户应该详细评估自己的处理是否能用传统的惯例如插件节点来解决。尽管如此,在一些情况下,插件解析器提供了一个复杂任务的解决方案。
清单 6. 完整的代码清单
#ifndef __MVS__
#include <malloc.h>
#endif
#include <windows.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <BipCci.h>
#include <BipCni.h>
#include <BipCpi.h>
#ifdef __cplusplus
extern "C" {
#endif
void LilFactoryExportPrefix * LilFactoryExportSuffix bipGetParserFactory(void);
#ifdef __cplusplus
}
#endif
#ifdef __MVS__
#define BIP_DEF_COMP_CCSID 1047
#else
#define BIP_DEF_COMP_CCSID 1208
#endif
#define CONST_PARSER_CLASS "CCILOG"
#define CONST_PARSER_FACTORY "ComIbmSampleCciLogParserFactory"
#define CONST_WBIMB_CATALOG "WMQIv210"
#define CONST_VIRTUALTRACENODENAME "CciLogParser - Extension for ESQL"
CciChar * constParserClass = 0;
CciChar * constParserFactory = 0;
CciChar * constWBIMBCatalog = 0;
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
//
// the signature of cpiParseBufferEncoded() and cpiWriteBufferEncoded() is
// the same and their body is empty, so just this one function is used
int cpiParseAndWriteBufferEncoded(CciParser* parser, CciContext* context,
int encoding, int ccsid) {
return 0;
}
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
CciContext* cpiCreateContext(CciParser* parser) {
return((CciContext*) 0);
}
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
//
// the signature of cpiParseFirstChild(), cpiParseLastChild(),
// cpiParsePreviousSibling() and cpiParseNextSibling() is the
// same and their body is empty, so just this one function is used
void cpiParseFirstLastChildAndPreviousNextSibling(CciParser* parser,
CciContext* context, CciElement* element) {
return;
}
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
void cpiSetElementValue(CciParser* parser, CciElement* element, CciElementValue* value) {
CciElement* valueElement;
CciSize bufferSize;
int rc;
CciChar buffer[1024]; // maximum supported length of message
char charBuffer[1024];
// set the value
valueElement = element;
if ((cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_VALUE) ||
(cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME_VALUE)) {
cpiSetElementValueValue(&rc, element, value);
} else if (cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME) {
/* Create a new value element, add as a first child, and set the value */
valueElement = cpiCreateElement(&rc, parser);
cpiSetElementType(&rc, valueElement, CCI_ELEMENT_TYPE_VALUE);
cpiSetElementValueValue(&rc, valueElement, value);
cpiAddAsFirstChild(&rc, element, valueElement);
}
buffer[0] = 0;
bufferSize = cpiElementCharacterValue(&rc, valueElement, buffer, 1023);
if (rc==CCI_EXCEPTION) {
cciRethrowLastException(&rc);
} else if (rc==CCI_SUCCESS) {
cciUcsToMbs(&rc, buffer, charBuffer, 1023, BIP_DEF_COMP_CCSID);
if (rc!=CCI_SUCCESS) {
// Error occured, e.g the message contained non-ASCII
// characters which were expanded to more than one byte per character.
sprintf(charBuffer, "CciLogParser.lil: error converting the log text "
"into a C-string, error code %i", rc);
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
} else {
// the original intend of this parser; write the message to the WMQI log
cciLog(&rc, CCI_LOG_INFORMATION, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
}
} else if (rc==CCI_BUFFER_TOO_SMALL) {
// here we could allocate a new buffer and retrieve the text again,
// but this sample just writes an error to the log
strcpy(charBuffer, "CciLogParser.lil: buffer too small for conversion, "
"pass only ASCII style messages");
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
} else {
// unhandled error
sprintf(charBuffer,
"CciLogParser.lil: unknown error rc=%i while retrieving the text", rc);
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "cpiSetElementValue()",
constWBIMBCatalog, 3001, charBuffer,
charBuffer, CONST_VIRTUALTRACENODENAME, 0);
}
return;
}
// -----------------------------------------------------------------------------
// Plugin Parser Implementation Function
const CciElementValue* cpiElementValue(CciParser* parser, CciElement* element) {
CciElement* firstChild;
const CciElementValue* value;
int rc;
if ((cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_VALUE) ||
(cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME_VALUE)) {
value = cpiElementValueValue(&rc, element);
}
else if (cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME) {
firstChild = cniFirstChild(&rc, element);
value = cpiElementValueValue(&rc, firstChild);
}
else {
}
return(value);
}
// -----------------------------------------------------------------------------
// utility function
const CciChar* CciString(const char* source, int codepage) {
// maximum number of characters in Unicode representation
int rc;
int maxChars = strlen(source) + 1 ;
CciChar* buffer = (CciChar*) malloc(maxChars * sizeof(CciChar));
cciMbsToUcs(&rc, source, buffer, maxChars, codepage);
return buffer;
}
// -----------------------------------------------------------------------------
// utility function
void initParserConstants(void) {
constParserClass = (CciChar*) CciString(CONST_PARSER_CLASS, BIP_DEF_COMP_CCSID);
constParserFactory = (CciChar*) CciString(CONST_PARSER_FACTORY, BIP_DEF_COMP_CCSID);
constWBIMBCatalog = (CciChar*) CciString(CONST_WBIMB_CATALOG, BIP_DEF_COMP_CCSID);
}
// -----------------------------------------------------------------------------
// entry point for WMQI
void LilFactoryExportPrefix * LilFactoryExportSuffix bipGetParserFactory(void) {
CciFactory* factoryObject;
int rc;
static CPI_VFT vftable = {CPI_VFT_DEFAULT};
// initialize the constants used by this plug-in
initParserConstants();
// setup function table with pointers to parser implementation functions
vftable.iFpParseBufferEncoded = cpiParseAndWriteBufferEncoded;
vftable.iFpWriteBufferEncoded = cpiParseAndWriteBufferEncoded;
vftable.iFpSetElementValue = cpiSetElementValue;
vftable.iFpElementValue = cpiElementValue;
// mandatory functions, otherwise WMQI does not use this parser
vftable.iFpCreateContext = cpiCreateContext;
vftable.iFpParseFirstChild = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParseLastChild = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParsePreviousSibling = cpiParseFirstLastChildAndPreviousNextSibling;
vftable.iFpParseNextSibling = cpiParseFirstLastChildAndPreviousNextSibling;
// create the parser factory for this plugin
factoryObject = cpiCreateParserFactory(&rc, constParserFactory);
if (factoryObject) {
// Define the classes of message supported by the factory
cpiDefineParserClass(&rc, factoryObject, constParserClass, &vftable);
} else {
static char* errMsg = "CciLogParser.lil: error creating the parser factory "
CONST_PARSER_FACTORY;
cciLog(&rc, CCI_LOG_ERROR, __FILE__, __LINE__, "bipGetParserFactory()",
constWBIMBCatalog, 3001, errMsg, errMsg, CONST_VIRTUALTRACENODENAME, 0);
}
return(factoryObject);
}
|
参考资料
关于作者  | |  | Martin Smolny 工作于 IBM 位于 Boeblingen 的开发实验室的 WebSphere Process Choreographer 开发组。他正从事于 Financial Networks 产品的 WebSphere Business Integration,该产品是在 WebSphere MQ Integrator 之上构造的. Martin 在 Stuttgart 完成了信息技术的学习并在 1998 年加入了 IBM 德国分部。 |
对本文的评价
|  |