Consuming Web services from RPG or COBOL programs on System i

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.

Share:

Jerome Tarte (jerome.tarte@fr.ibm.com), Advisory IT Architect, IBM 

Jerome TarteJerome 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 (p_guerton@fr.ibm.com), IT Specialist, IBM

Photo of Philippe GuertonPhilippe 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.



21 May 2007

Also available in Japanese

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
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

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into SOA and web services on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services
ArticleID=224916
ArticleTitle=Consuming Web services from RPG or COBOL programs on System i
publish-date=05212007