Remoting NG

Remoting NG Tutorial Part 3: Consuming SOAP/WSDL Web Services

Welcome Again

Welcome to part three of the Remoting tutorial. This part of the Remoting NG tutorial shows how to invoke a third-party SOAP web service defined in a WSDL document.

This tuturial assumes that you are familiar with basic POCO C++ Libraries programming techniques. You should also have read the Remoting Overview and be familiar with basic Remoting and SOAP concepts, such as presented in part 1 and part 2 of the Remoting tutorial.

From a programming point of view, invoking a third-party web service is not different from invoking a Remoting NG web service. The same classes — interface, proxy and client helper are used with the SOAP transport. What's new is the XSD/WSDL code generator — XSDGen — which is used to generate C++ classes for services and types defined in the WSDL document.

C++ code generation from a WSDL document is a two-step process. First, XSDGen parses the WSDL (and any referenced XML Schemas) and generates interface classes for the services, as well as classes for all complex types defined in the schema. Then, RemoteGenNG is used to generate the Remoting infrastructure code.

Figure 1 illustrates the code generation process from WSDL to C++ client code.

Figure 1: WSDL to C++ Code Generation
Figure 1: WSDL to C++ Code Generation

Generating C++ Code From a WSDL Document

The first step when invoking a web service defined by a WSDL document is to run the XSDGen code generator, which generates C++ classes for the types defined in the WSDL. The generated classes will be annotated with Remoting attributes. In the second step, the header files generated by XSDGen will be processed by RemoteGenNG to generate the necessary Remoting infrastructure classes (serializers, deserializers, proxy, client helper, etc.).

In order to run the XSDGen code generator, a configuration file must be created, similar to the configuration file for RemoteGen. This configuration file tells the code generator where to put the generated header and implementation files, and how to map XML namespaces to C++ namespaces.

For this tutorial, we are going to use the public VIES VAT Number Validation service provided by the European Commission. In addition to the form-based validation system, a SOAP web service is available. The WSDL for the service can be found at <http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl>. The service provides two operations: checkVat and checkVatApprox.

The WSDL defines two XML namespaces, urn:ec.europa.eu:taxud:vies:services:checkVat:types and urn:ec.europa.eu:taxud:vies:services:checkVat. The configuration file must contain a mapping for each XML namespace to a C++ namespace. Therefore, the configuration file XSDGen.xml for this WSDL looks as follows:

<AppConfig>
    <XSDGen>
        <default>
            <include>include/CheckVat</include>
            <src>src</src>
            <includeRoot>include</includeRoot>
        </default>
        <schema targetNamespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
            <include>include/CheckVat/Types</include>
            <namespace>CheckVat::Types</namespace>
        </schema>
        <schema targetNamespace="urn:ec.europa.eu:taxud:vies:services:checkVat">
            <namespace>CheckVat</namespace>
        </schema>
    </XSDGen>
</AppConfig>

We have a mandatory default element which defines all settings that apply to all schema namespaces unless explicitly overridden. Specifically, in our configuration file, we define the directories for the generated header and implementation files.

For each of the two namespaces in the WSDL there is a schema element specifying the mapping to a C++ namespace, as well as possibly overriding other default settings (e.g. the directory for generated header files).

Note that XSDGen will abort with an error message if it cannot find a schema element for every target XML namespace used in the WSDL.

We need to pass the location of the WSDL document to the code generator. The location can either be a local filesystem path or a URL (http or ftp).

With the configuration file ready, on a Unix system we can invoke the code generator:

$ XSDGen --config=XSDGen.xml http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl

On a Windows system, the command syntax is slightly different:

C:\CheckVat> XSDGen /config=XSDGen.xml http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl

The following files will be generated:

include/
    CheckVat/
        CheckVatService.h
        Types/
            CheckVat.h
            CheckVatApprox.h
            CheckVatApproxResponse.h
            CheckVatResponse.h
src/
    CheckVat.cpp
    CheckVatApproxResponse.cpp
    CheckVatService.cpp
    CheckVatApprox.cpp
    CheckVatResponse.cpp

CheckVatService.h is the most important file, as it defines the C++ interface for invoking the web service. All other files are actually not needed. They have been generated anyway, because the code generator generates a C++ class for every complex type definition it finds in the WSDL document. In our case, these are all types defining the request and response messages for the SOAP request. This information has been included in the generated interface class, so these files are not needed.

Let's look at the generated interface file, CheckVatService.h.

// 
// CheckVatService.h
// 
// This file has been generated.
// Warning: All changes to this will be lost when the file is re-generated.
// 
// 


#ifndef CheckVat_CheckVatService_INCLUDED
#define CheckVat_CheckVatService_INCLUDED


#include "Poco/DateTime.h"
#include "Poco/Nullable.h"
#include "Poco/Optional.h"
#include "Poco/SharedPtr.h"
#include <string>


namespace CheckVat {


//@ name=checkVatService
//@ namespace="urn:ec.europa.eu:taxud:vies:services:checkVat"
//@ remote
class CheckVatService
{
protected:
    virtual ~CheckVatService();


public:
    //@ action=""
    //@ name=checkVat
    //@ namespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types"
    //@ replyName=checkVatResponse
    //@ $countryCode={direction=in, name=countryCode}
    //@ $vatNumber={direction=in, name=vatNumber}
    //@ $countryCode2={direction=out, name=countryCode}
    //@ $vatNumber2={direction=out, name=vatNumber}
    //@ $requestDate={direction=out, name=requestDate}
    //@ $valid={direction=out, name=valid}
    //@ $name={direction=out, name=name}
    //@ $address={direction=out, name=address}
    virtual void checkVat(
        const std::string& countryCode, 
        const std::string& vatNumber, 
        std::string& countryCode2, 
        std::string& vatNumber2, 
        Poco::DateTime& requestDate, 
        bool& valid, 
        Poco::Nullable<std::string >& name, 
        Poco::Nullable<std::string >& address) = 0;

    //@ action=""
    //@ name=checkVatApprox
    //@ namespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types"
    //@ replyName=checkVatApproxResponse
    //@ $countryCode={direction=in, name=countryCode}
    //@ $vatNumber={direction=in, name=vatNumber}
    //@ $traderName={direction=in, name=traderName}
    //@ $traderCompanyType={direction=in, name=traderCompanyType}
    //@ $traderStreet={direction=in, name=traderStreet}
    //@ $traderPostcode={direction=in, name=traderPostcode}
    //@ $traderCity={direction=in, name=traderCity}
    //@ $requesterCountryCode={direction=in, name=requesterCountryCode}
    //@ $requesterVatNumber={direction=in, name=requesterVatNumber}
    //@ $countryCode2={direction=out, name=countryCode}
    //@ $vatNumber2={direction=out, name=vatNumber}
    //@ $requestDate={direction=out, name=requestDate}
    //@ $valid={direction=out, name=valid}
    //@ $traderName2={direction=out, name=traderName}
    //@ $traderCompanyType2={direction=out, name=traderCompanyType}
    //@ $traderAddress={direction=out, name=traderAddress}
    //@ $traderStreet2={direction=out, name=traderStreet}
    //@ $traderPostcode2={direction=out, name=traderPostcode}
    //@ $traderCity2={direction=out, name=traderCity}
    //@ $traderNameMatch={direction=out, name=traderNameMatch}
    //@ $traderCompanyTypeMatch={direction=out, name=traderCompanyTypeMatch}
    //@ $traderStreetMatch={direction=out, name=traderStreetMatch}
    //@ $traderPostcodeMatch={direction=out, name=traderPostcodeMatch}
    //@ $traderCityMatch={direction=out, name=traderCityMatch}
    //@ $requestIdentifier={direction=out, name=requestIdentifier}
    virtual void checkVatApprox(
        const std::string& countryCode, 
        const std::string& vatNumber, 
        const Poco::Optional<std::string >& traderName, 
        const Poco::Optional<std::string >& traderCompanyType, 
        const Poco::Optional<std::string >& traderStreet, 
        const Poco::Optional<std::string >& traderPostcode, 
        const Poco::Optional<std::string >& traderCity, 
        const Poco::Optional<std::string >& requesterCountryCode, 
        const Poco::Optional<std::string >& requesterVatNumber, 
        std::string& countryCode2, 
        std::string& vatNumber2, 
        Poco::DateTime& requestDate, 
        bool& valid, 
        Poco::Nullable<std::string >& traderName2, 
        Poco::Nullable<std::string >& traderCompanyType2, 
        Poco::Optional<std::string >& traderAddress, 
        Poco::Optional<std::string >& traderStreet2, 
        Poco::Optional<std::string >& traderPostcode2, 
        Poco::Optional<std::string >& traderCity2, 
        Poco::Optional<std::string >& traderNameMatch, 
        Poco::Optional<std::string >& traderCompanyTypeMatch, 
        Poco::Optional<std::string >& traderStreetMatch, 
        Poco::Optional<std::string >& traderPostcodeMatch, 
        Poco::Optional<std::string >& traderCityMatch, 
        std::string& requestIdentifier) = 0;
};


} // CheckVat


#endif // CheckVat_CheckVatService_INCLUDED

We can see that XSDGen has created a C++ class with two pure virtual methods corresponding to the two operations defined in the WSDL document. Every method has Remoting attributes mapping generated C++ names to the original XML element names and namespaces expected by the web service.

We see that most method parameters use the Poco::Optional or Poco::Nullable template. This is due to the usage of minOccurs="0" and/or nillable="true" attributes in the XML schema for the respective elements.

Generally, an XML element defined with minOccurs="0" will be mapped to Poco::Optional, while an element defined with nillable="true" will be mapped to Poco::Nullable. There can also be element with both attributes defined. In this case, the default is to map to Poco::Nullable. This can be changed with a configuration file setting.

Running The Remoting NG Code Generator

The generated header file must now be run through RemoteGenNG to generate the necessary infrastructure code. The configuration file for RemoteGenNG is shown below:

<AppConfig>
    <RemoteGen>
        <files>
            <include>
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/RemoteObject.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/Proxy.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/Skeleton.h
                include/CheckVat/CheckVatService.h
            </include>
        </files>
        <output>
            <mode>client</mode>
            <namespace>CheckVat</namespace>
            <include>include/CheckVat</include>
            <src>src</src>
            <includeRoot>include</includeRoot>
            <flatIncludes>false</flatIncludes>
            <timestamps>false</timestamps>
        </output>
        <compiler id="gcc">
            <exec>g++</exec>
            <options>
                -I${POCO_BASE}/Foundation/include
                -I${POCO_BASE}/RemotingNG/include
                -I./include
                -E
                -C
                -o%.i
            </options>
        </compiler>
        <compiler id="clang">
            <exec>clang++</exec>
            <options>
                -I${POCO_BASE}/Foundation/include
                -I${POCO_BASE}/RemotingNG/include
                -I./include
                -E
                -C
                -xc++
                -o%.i
            </options>
        </compiler>
        <compiler id="msvc">
            <exec>cl</exec>
            <options>
                /I "${POCO_BASE}\Foundation\include"
                /I "${POCO_BASE}\RemotingNG\include"
                /I ".\include"
                /nologo
                /C
                /P
                /TP
            </options>
        </compiler>
    </RemoteGen>
</AppConfig>

Note that if the web service uses custom parameter types, the header files for the corresponding classes generated by XSDGen must also be included in the files/include element of the configuration file, so that RemoteGenNG generates the required serializer and deserializer classes.

RemoteGenNG will generate the interface class, proxy and proxy factory, as well as the client helper class. With these classes available, we are ready to invoke the web service.

Invoking The Web Service

The code for invoking the web service is straightforward Remoting client code:

#include "CheckVat/CheckVatServiceClientHelper.h"
#include "Poco/RemotingNG/SOAP/TransportFactory.h"
#include <iostream>


int main(int argc, char** argv)
{
    try
    {
        // register transport
        Poco::RemotingNG::SOAP::TransportFactory::registerFactory();

        // get proxy for CheckVatService web service
        CheckVat::ICheckVatService::Ptr pVatService = CheckVat::CheckVatServiceClientHelper::find(
            "http://ec.europa.eu/taxation_customs/vies/services/checkVatService", 
            "soap"
        );

        std::string countryCode;
        std::string vatNumber;
        Poco::DateTime dateTime;
        bool valid;
        Poco::Nullable<std::string> name;
        Poco::Nullable<std::string> address;

        // invoke web service
        pVatService->checkVat("AT", "U62417138", countryCode, vatNumber, dateTime, valid, name, address);

        std::cout << "countryCode: " << countryCode << std::endl;
        std::cout << "vatNumber:   " << vatNumber << std::endl;
        std::cout << "valid:       " << (valid ? "true" : "false") << std::endl;
        if (!name.isNull()) 
            std::cout << "name:        " << name.value() << std::endl;
        if (!address.isNull())
            std::cout << "address:     " << address.value() << std::endl; 
    }
    catch (Poco::Exception& exc)
    {
        std::cerr << exc.displayText() << std::endl;
        return 1;
    }
    return 0;
}

We'll need to include the client helper class and SOAP transport factory. First we register the SOAP transport, and then we use the client helper to create a proxy object for invoking the web service. Note that the URI for invoking the web service can be found in the WSDL document.

Invoking the checkVat operation is, as usual, just a normal method call on the proxy object. For testing purposes, we use the VAT ID of Applied Informatics as argument.

The result delivered by the web service is then simply written to standard output.

This concludes part three of the Remoting NG tutorial. Part four, the last part of the tutorial, will show how to provide and consume RESTful web services with Remoting NG.