To access a remote Web Service, you first must get the WSDL information from the service provider. Sample services can be found through UDDI registries (http://www.uddi.org), or on other sites such as XMethods (http://www.xmethods.net).
You can write a GWS Server application for a Web Service that you have created; see Tutorial: Writing a Server Application. However, if you want to make sure your Web Service is compatible with that of a third-party (an accounting application vendor, for example), you can use the fglwsdl tool to obtain the WSDL information that complies with that vendor's standards, and to generate corresponding files that can be used in your GWS Server application.
The following example requests the Calculator Web Service information from the specified URL, and the output files will have the base name "ws_calculator".
fglwsdl -s -o ws_calculator http://localhost:8090/Calculator?WSDL
For a server application, fglwsdl generates two files, which should not be modified:
<filename>.inc - the globals file, containing declarations of global variables that can be used as input or output to functions accessing the Web Service operations. In our example, the file is ws_calculatorService.inc.
This file must be listed in a GLOBALS statement at the top of any .4gl modules that you write for your GWS Server application.
This file must be compiled and linked into your GWS Server application.
The COM library enables to intercept high-level web services operation on server side. You can now define three 4GL functions via the following methods of the web service class. They will be executed at different steps of a web service request processing in order to modify the SOAP request, response or the generated WSDL document before or after the SOAP engine has processed it. This helps handle WS-* specifications not supported in the web service API.
All three kinds of 4GL callback functions must conform to the following prototype:
FUNCTION CallbackHandler( doc xml.DomDocument ) RETURNING xml.domDocument
Register your handler with:
CALL serv.registerWsdlHandler("WSDLHandler")
where serv is of class com.WebService and WSDLHandler is the following function:
FUNCTION WSDLHandler(wsdl) DEFINE wsdl Xml.DomDocument DEFINE node Xml.DomNode DEFINE list Xml.DomNodeList DEFINE ind INTEGER DEFINE name STRING # Add a comment LET node = wsdl.createComment("First modified WSDL via a 4GL callback function") CALL wsdl.prependDocumentNode(node) # Rename input and output parameter in UPPERCASE LET list = wsdl.selectByXPath("//wsdl:definitions/wsdl:types/xsd:schema/xsd:complexType/xsd:sequence/xsd:element/xsd:complexType/xsd:sequence/xsd:element",NULL) FOR ind=1 TO list.getCount() LET node = list.getItem(ind) LET name = node.getAttribute("name") LET name = name.toUpperCase() CALL node.setAttribute("name",name) END FOR RETURN wsdl END FUNCTIONIf NULL is returned from the callback function, an HTTP error will be sent and the processServices() returns error code -20.
Register your handler with:
CALL serv.registerInputRequestHandler("InputRequestHandler")
where serv is of class com.WebService and InputRequestHandler is the following function:
FUNCTION InputRequestHandler(in) DEFINE in Xml.DomDocument DEFINE ind INTEGER DEFINE node Xml.DomNode DEFINE copy Xml.DomNode DEFINE tmp Xml.DomNode DEFINE parent Xml.DomNode DEFINE name STRING DEFINE list Xml.DomNodeList # Change input parameter below myrecord in lower case to follow high-level web service LET list = in.SelectByXPath("//SOAP:Envelope/SOAP:Body/fjs:EchoDOCRecordRequest/fjs:myrecord/*","SOAP","http://schemas.xmlsoap.org/soap/envelope/","fjs","http://www.mycompany.com/webservices") FOR ind = 1 TO list.getCount() LET node = list.getItem(ind) LET parent = node.getParentNode() LET name = node.getLocalName() LET copy = in.createElementNS(node.getPrefix(),name.toLowerCase(),node.getNamespaceURI()) LET tmp = node.getFirstChild() LET tmp = tmp.clone(true) CALL copy.appendChild(tmp) CALL parent.replaceChild(copy,node) END FOR RETURN in END FUNCTIONIf NULL is return from the callback function, a SOAP fault will be sent (but can be changed from the output handler) and the processServices() returns error code -18.
Register your handler with:
CALL serv.registerOutputRequestHandler("OutputRequestHandler")
where serv is of class com.WebService and OutputRequestHandler is the following function:
FUNCTION OutputRequestHandler(out) DEFINE out Xml.DomDocument DEFINE ind INTEGER DEFINE node Xml.DomNode DEFINE copy Xml.DomNode DEFINE tmp Xml.DomNode DEFINE parent Xml.DomNode DEFINE name STRING DEFINE list Xml.DomNodeList # Change output parameter below myrecord in uppercase before sending back to the client LET list = out.SelectByXPath("//SOAP:Envelope/SOAP:Body/fjs:EchoDOCRecordResponse/fjs:myrecord/*","SOAP","http://schemas.xmlsoap.org/soap/envelope/","fjs","http://www.mycompany.com/webservices") FOR ind = 1 TO list.getCount() LET node = list.getItem(ind) LET parent = node.getParentNode() LET name = node.getLocalName() LET copy = out.createElementNS(node.getPrefix(),name.toUpperCase(),node.getNamespaceURI()) LET tmp = node.getFirstChild() LET tmp = tmp.clone(true) CALL copy.appendChild(tmp) CALL parent.replaceChild(copy,node) END FOR RETURN out END FUNCTIONIf NULL is return from the callback function, a SOAP fault will be sent and the processServices() returns error code -19.
In the generated file ws_calculatorService.inc, the definitions of the variables for the input and output record are the same as those generated for the Web Service Client application:
#VARIABLE : Add -- defines the global INPUT record DEFINE Add RECORD ATTRIBUTE(XMLName="Add",
XMLNamespace="http://tempuri.org/")
a INTEGER ATTRIBUTE(XMLName="a",XMLNamespace=""),
b INTEGER ATTRIBUTE(XMLName="b",XMLNamespace="")
END RECORD
# VARIABLE : AddResponse -- defines the global OUTPUT record DEFINE AddResponse RECORD ATTRIBUTE(XMLName="AddResponse",
XMLNamespace="http://tempuri.org/")
r INTEGER ATTRIBUTE(XMLName="r",XMLNamespace="")
END RECORD
The generated file ws_calculatorService.4gl contains a single function that creates the Calculator service, creates and publishes the service operations, and registers the Calculator service:
FUNCTION Createws_calculatorService()
DEFINE service com.WebService
DEFINE operation com.WebOperation
...
# Create Web Service
LET service = com.WebService.CreateWebService("Calculator","http://tempuri.org/")
# Publish Operation : Add
LET operation = com.WebOperation.CreateRPCStyle("Add","Add",Add,AddResponse)
CALL service.publishOperation(operation,"")
...
# Register Service
CALL com.WebServiceEngine.RegisterService(service)
RETURN 0
...
END FUNCTION
The ws_calculator.inc file provides you with the global input and output records and function names that allow you to write your own code implementing the Add operation. Your new code should not be written in the generated modules. For example, do not add your own version of the Add function to the generated ws_calculator.4gl module; it can be included in your module containing the MAIN program block, or in a separate module to be included as part of the Web server application. The function must use the generated definitions for the global input and output records.
In your version of the operation, this function adds 100 to the sum of the variables in the input record:
FUNCTION Add() LET AddResponse.r = (Add.a + Add.b) + 100 END FUNCTION
See Tutorial: Writing a Server application for more information. The demo/WebServices subdirectory of your Genero installation directory contains complete examples of Server Applications.