 | Level: Advanced Jerome Tarte (jerome.tarte@fr.ibm.com), Advisory IT Architect, IBM Philippe Guerton (p_guerton@fr.ibm.com), IT Specialist, IBM
21 May 2007 Learn how to use the IBM Web Services Client for C++
to generate Web service client stubs and libraries containing programs
to manage SOAP messages and send them over HTTP.
Introduction
There is a growing need for RPG applications (see
Resources for a link to more information about
RPG applications) to be able to access information available through
Web services and to be able to work with XML data from a variety of
sources. While the XML Toolkit for iSeries™ provides access to
both SAX and DOM parsers (see Resources for a
link to more information about the XML toolkit), the toolkit still
requires a significant amount of programming to handle the mapping of
the XML data to fields and structures commonly used in RPG programs.
Creating the SOAP message and sending it over HTTP requires a
significant programming effort and a good knowledge of the SOAP
protocol.
To simplify the Web service invocation, IBM offers another toolkit
along with the XML toolkit that allows you to easily invoke Web
services without mastering the SOAP protocol. The IBM Web Services
Client for C++ provides tools to generate Web service client stubs, and
it provides libraries containing programs to manage SOAP messages to
send them over HTTP. See Resources for a link
to more information about the Web Services Client for C++ toolkit.
Understanding the Web
Services Client for C++ toolkit architecture
The Web Services Client for C++ toolkit uses externally described
data structures to define the mapping between elements of XML documents
and fields usable by RPG applications. It also provides the capability
to send and receive XML documents through HTTP requests, including the
generation of the HTTP headers required for SOAP processing.
Figure 1 shows the architecture you would follow when you use the
toolkit to invoke Web services from Integrated Language Environment
(ILE) programs.
Figure 1. Architecture
when using Web Services Client for C++ toolkit
Introducing
the Web service example
This article describes a basic Web service example that converts an
amount expressed in euros to the same amount expressed in another
currency. The Web service is implemented as a Java™ bean. The
business code of the service is shown in Listing 1.
Listing 1. Business code of
Web service toolkit
…
/**
* convert am amount in euro into an amount in the currency given in parameter
* @param currency the targeted currency
* @param amount, the amount
* @return the converted amount.
*/
public float convert(String currency, float amount)
{
float result = (float)(getRate(currency)*amount);
return result;
}
….
|
Web services are described by files called Web Service Description
Language files (or WSDLs), which are XML files containing all the
information related to the services that are available at a particular
location on the internet. At their simplest level, WSDLs describe
request-and-response message pairs in detail, and WSDLs contain
everything relevant to the services. The WSDL used for the example in
this article is shown in Listing 2.
Listing 2. WSDL file for example
<wsdl:definitions targetNamespace="http://currencyquote.ibm.com"
xmlns:impl="http://currencyquote.ibm.com"
xmlns:intf="http://currencyquote.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsi="http://ws-i.org/profiles/basic/1.1/xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema targetNamespace="http://currencyquote.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://currencyquote.ibm.com"
xmlns:intf="http://currencyquote.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element name="convertResponse">
<complexType>
<sequence>
<element name="convertReturn" type="xsd:float"/>
</sequence>
</complexType>
</element>
<element name="convert">
<complexType>
<sequence>
<element name="currency" nillable="true" type="xsd:string"/>
<element name="amount" type="xsd:float"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
<wsdl:message name="convertResponse">
<wsdl:part element="impl:convertResponse" name="parameters"/>
</wsdl:message>
<wsdl:message name="convertRequest">
<wsdl:part element="impl:convert" name="parameters"/>
</wsdl:message>
<wsdl:portType name="CurrencyQuoteBean">
<wsdl:operation name="convert">
<wsdl:input message="impl:convertRequest" name="convertRequest"/>
<wsdl:output message="impl:convertResponse" name="convertResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CurrencyQuoteBeanSoapBinding"
type="impl:CurrencyQuoteBean">
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="convert">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="convertRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="convertResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CurrencyQuoteBeanService">
<wsdl:port binding="impl:CurrencyQuoteBeanSoapBinding"
name="CurrencyQuoteBean">
<wsdlsoap:address location=
"http://localhost:9080/CurrencyQuote/services/CurrencyQuoteBean"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
 |
Generating
a Web services proxy
You can use a Java program, WSDL2WS, which is part of the Web
Services Client for C++ package, to turn the WSDL into a suite of C++
stubs and data objects that you can call and pass information to. The
C++ stubs and data objects also request information from the server and
then wait for the corresponding reply before passing the response data
objects back to the client. The stubs hide the network communication
from the application writer. All you need to know is the name of the
service, the method it contains, and the structure of any data objects
that are passed.
To generate the proxy of the sample, use the following command from
the QShell:
/wscc-1.1.0-os400/bin/wsdl2ws.sh CurrencyQuoteBean.wsdl
|
The WSDL2WS tool has some options:
-
-lc generates C proxy. The default
output is C++.
-
-v generates more tracing during the
generation.
-
-otarget_directory indicates where the
output is generated.
The outputs are files of types .cpp and
.h. You get one C++ file representing the
service and one for non-basic type data that the service uses. For the
currency quote example, you get only
CurrencyQuoteBean.cpp and
CurrencyQuoteBean.h. Actually, the toolkit
has a restriction on the WSDL file. Any WSDLs used must define one and
only one service with one and only one port type.
Determining
whether to use C or C++ proxy
The WSDL2WS allows the generation of C or C++ code. The choice of
output language choice is important. There are two aspects to consider:
- C++ code is more difficult to call from RPG or COBOL programs.
These two languages are not object-oriented, so it is easier to call a
C function than a C++ method of an object.
- The structure of the WSDL is close to object-oriented syntax with
the XML parameter description. It is useful to use object-oriented
syntax when you develop a Web service request.
The usage of a C wrapper between the RPG or COBOL code and the
generated C++ code is a good compromise. This solution allows the RPG
or COBOL to call a C function. The C function mimics C++ behavior by
implementing the call to the Web service by using the C++ proxy.
To export the function that is called from an RPG or COBOL module,
you will use a #pragma map directive, as
follows:
#pragma map (convert(xsd__string,float), "CONVERT")
|
By using a C wrapper in front of generated C++ code, this declaration
will be in the C wrapper. So, there is no need to modify the generated
code. Listing 3 shows the code of the C wrapper for the example.
Listing 3. Example C wrapper
#include <iostream>
#include <string.h>
#include "CurrencyQuoteBean.hpp"
using namespace std;
#pragma map (convert(xsd__string,float), "CONVERT")
float convert(xsd__string devise, float amount)
{
// create the service proxy object
// give the service port as parameter
CurrencyQuoteBean* cb = new CurrencyQuoteBean("http://
9.212.15.63:9080/CurrencyQuote/services/CurrencyQuoteBean") ;
// invoke the web service by calling the business method.
float result = cb->convert(devise,amount);
return result;
}
|
Calling from RPG
The C++ and C code handle the Web services call. On the RPG side, the
code manages only the integration with the C/C++ module. There is
nothing specific to the Web services. The integration needs the
declaration of the C function (convert in
the sample) with its parameter and the type of return. See Listing 4
for the RPG program that invokes the convert method.
Listing 4. RPG program invoking convert
method
H NOMAIN
Dconvert PR 8F
D P_currency 6A
D P_amount 8F
DconvertTo PR 15P 5
D P_curr 256A
D P_amnt 15P 5
PconvertTo B EXPORT
DconvertTo PI 15P 5
D P_curr 256A
D P_amnt 15P 5
D cvt S 8F
D amount S 8F
D converted S 15P 5
D currency S 6A
C EVAL currency = P_curr
C Z-ADD P_amnt amount
C EVAL cvt = convert(currency:amount)
C Z-ADD cvt converted
C RETURN converted
PconvertTo E
|
Calling from COBOL
As with the RPG program, calling Web services by using techniques
from the COBOL program is a matter of integration between COBOL and C
programs. In the COBOL program, the definition of the C function must
be declared in the Special-Names section. Once the declaration is done,
the program can invoke the C function by using a Call Procedure
sentence. This is illustrated in Listing 5.
Listing 5. COBOL program invoking
convert method
Identification Division.
Program-ID. WRAPCBL.
Author.
Environment Division.
Configuration Section.
Source-Computer. IBM-ISERIES.
Object-Computer. IBM-ISERIES.
Special-Names. LINKAGE PROCEDURE FOR "convert"
USING ALL DESCRIBED.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 DEVISE PIC x(6).
01 AMOUNT USAGE IS COMP-1.
01 RETVAL USAGE IS COMP-1.
Procedure Division.
Main.
string "dollar" delimited by size X"00"
delimited by size INTO devise.
ADD 123.45 to amount.
Call Procedure "convert" using DEVISE
By value AMOUNT
returning INTO RETVAL.
DISPLAY RETVAL.
|
Compiling
and binding
For the compilation of C and C++ source files, the
include directory must define the
include directory provided by the Web
Service Client for C++ toolkit. This directory is localized in the
wscc-1.1.0-os400 installation directory (by
default
/QIBM/ProdData/xmltoolkit/wscc-1.0-OS400).
When you create the program or service program that includes the C++
proxy module, you must define a binding to the AXIS service program (by
default, /QXMLTOOLS/QAXIS10C). In order to
facilitate the program generation, use the CL script containing all the
compilation and link commands shown in Listing 6.
Listing 6. CL script creating the
currency quote sample program
PGM
ADDLIBLE LIB(BOP) POSITION(*FIRST)
MONMSG MSGID(CPF2103)
CRTCPPMOD MODULE(ILETOWS/CURRMODC) +
SRCSTMF('/CTC_assets/ILEtoWS/CurrencyQuoteBean.cpp+
') DBGVIEW(*SOURCE) REPLACE(*YES) +
INCDIR('/QIBM/ProdData/xmltoolkit/wscc-1.0-OS400/include')
CRTCPPMOD MODULE(ILETOWS/CURRWRAP) +
SRCSTMF('/CTC_assets/ILEtoWS/wrapper.c') +
DBGVIEW(*SOURCE) REPLACE(*YES) +
INCDIR('/ QIBM/ProdData/xmltoolkit/wscc-1.0-OS400/include')
CRTRPGMOD MODULE(ILETOWS/CVTCURR) SRCFILE(ILETOWS/QRPGLESRC) +
SRCMBR(CVTCURR) DBGVIEW(*SOURCE) REPLACE(*YES)
CRTCLMOD MODULE(ILETOWS/TESTCVT) SRCFILE(ILETOWS/QCLLESRC) +
SRCMBR(TESTCVT) REPLACE(*YES) DBGVIEW(*SOURCE)
CRTPGM PGM(ILETOWS/RPGTOWS) MODULE(ILETOWS/TESTCVT +
ILETOWS/CVTCURR ILETOWS/CURRWRAP +
ILETOWS/CURRMODC) ENTMOD(*FIRST) +
BNDSRVPGM(QXMLTOOLS /QAXIS10C) ACTGRP(*CALLER) +
REPLACE(*YES) STGMDL(*SNGLVL)
|
Understanding the
structure and object
Most Web services do not use only basic types
(String, int, float …) as input and output
parameters; they also use complex types. So, the client needs to be
able to handle more complex types. In this second example, the Web
service answers with a complex type Address
that contains several attributes. Listing 7 shows the WSDL file that
describes the second Web service.
Listing 7. WSDL describing second Web
service
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://user.ibm.com"
xmlns:impl="http://user.ibm.com"
xmlns:intf="http://user.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsi="http://ws-i.org/profiles/basic/1.1/xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema targetNamespace="http://user.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://user.ibm.com" xmlns:intf="http://user.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element name="getAddressResponse">
<complexType>
<sequence>
<element name="getAddressReturn" nillable="true" type="impl:Address"/>
</sequence>
</complexType>
</element>
<element name="getAddress">
<complexType>
<sequence>
<element name="id" type="xsd:int"/>
</sequence>
</complexType>
</element>
<complexType name="Address">
<sequence>
<element name="number" type="xsd:int"/>
<element name="street" nillable="true" type="xsd:string"/>
<element name="town" nillable="true" type="xsd:string"/>
<element name="zipCode" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</schema>
</wsdl:types>
<wsdl:message name="getAddressResponse">
<wsdl:part element="impl:getAddressResponse" name="parameters"/>
</wsdl:message>
<wsdl:message name="getAddressRequest">
<wsdl:part element="impl:getAddress" name="parameters"/>
</wsdl:message>
<wsdl:portType name="User">
<wsdl:operation name="getAddress">
<wsdl:input message="impl:getAddressRequest" name="getAddressRequest"/>
<wsdl:output message="impl:getAddressResponse" name="getAddressResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="UserSoapBinding" type="impl:User">
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getAddress">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="getAddressRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getAddressResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="UserService">
<wsdl:port binding="impl:UserSoapBinding" name="User">
<wsdlsoap:address location="http://localhost:9080/CurrencyQuote/services/User"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
By using the WSDL2WS tool, you get two file CPP files:
- A class representing the Web service client stub.
- A class representing the complex type
Address.
Use a C wrapper to avoid the problem of manipulating a C++ object
from RPG. The example defines a basic structure to represent the
address in the .h file. The use of a C
wrapper with a structure definition in the
.h file allows a loose coupling between the
RPG code and the generated code. You have better control if you define
the data structure that is manipulated in the RPG. Listing 8 shows that
in the .h file, a string is not represented
as String or as a pointer in memory but as
an array of character.
Listing 8. .h file
#if !defined(__CURRENCYQUOTEBEAN_CLIENTSTUB_H__INCLUDED_)
#define __CURRENCYQUOTEBEAN_CLIENTSTUB_H__INCLUDED_
#include "user.hpp";
struct MyAddress
{
int number;
char street[256];
char town[256];
char zipcode[256];
};
MyAddress * getAddress2(int id);
int getAddressPointer(int id, MyAddress* myAddress);
#endif /* !defined(__CURRENCYQUOTEBEAN_H__INCLUDED_) */
|
Listing 9 shows the structure of the C wrapper.
Listing 9. Structure of the C wrapper
#include <iostream>
#include <string.h>
#include "wrapper2.h";
using namespace std;
#pragma map (getAddress2(int), "GETADDRESS2")
MyAddress * getAddress2(int id)
{
User * user = new User("http://9.212.15.63:9080/CurrencyQuote/services/User") ;
//allocate Memory for the structure
MyAddress * myaddr = (MyAddress*) malloc(sizeof(MyAddress));
//invoke the service
Address * address = user->getAddress(id);
xsd__string str = address->street;
//copy the attribute from the web service result to the structure
myaddr->number = address->number;
strcpy(myaddr->street,address->street);
strcpy(myaddr->town,address->town);
strcpy(myaddr->zipcode,address->zipCode);
return myaddr;
}
|
In the RPG code, you need to define the structure used to exchange
the address. The strings are represented as arrays of 256 characters,
as shown in Listing 10.
Listing 10. Structure of the C wrapper
H NOMAIN
*
* Following are the data fields for the RPG program
DpRtnData S 16*
DADDRESS DS BASED(pRtnData)
D number 10I 0
D street 256A
D town 256A
D zipcode 256A
Did S 8S 0
Drc S 5I 0
Dgetaddress PR 16* EXTPROC('GETADDRESS2')
D P@Id 8S 0
DgetaddressA PR 256A
*
* Following prototype describes the C procedure being called
PGetAddressA B EXPORT
DgetaddressA PI 256A
*
* Code
C eval id = 10
C eval pRtnData = getaddress(id)
C return Street
PGetAddressA E
|
Conclusion
In this article, you saw how RPG, COBOL, and any ILE program on
i5/OS®, in general, can become a Web services consumer. With this
technical possibility, ILE programs can reuse existing services that
are exposed inside or outside the company, independently of their
implementations.
Resources Learn
Get products and technologies
- Build your
next development project with
IBM trial software
for download directly from developerWorks.
Discuss
About the authors  | 
|  | Jerome Tarte is an IT Architect from Europe Business Solutions Center at La Gaude, France. For the last nine years, he has been involved in many J2EE projects. The last three years his work has focused on SOA and Web Services. Today, as a member of the IBM System and Technology Group Lab Services Europe, he provides service for leading-edge IBM software and hardware for the IBM Systems, including Architecture Design, SOA, WebSphere, High Availability, and more. |
 | 
|  | Philippe Guerton is an IT specialist from IBM European Business Solutions Center at La Gaude, France. For the last nine years, he has been involved in many System i5 Integrated Language Environment projects as well as SAP implementations on System i5. For the last three years, he has focused on J2EE, concentrating mainly on the Web enablement of native 5250 applications. Today, as a member of the IBM System and Technology Group Lab Services Europe, he provides services for leading-edge technologies running on IBM Systems hardware platforms. His focus includes high availability systems, WebSphere Portal, and back-end integration, such as SAP, JDE, and 5250 applications. |
Rate this page
|  |