Back to Contents


Server stub and handlers

Topics

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


Generating files for a GWS Server

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:


Server handlers

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

Example 1 : Modify the generation of a WSDL

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 FUNCTION
If NULL is returned from the callback function, an HTTP error will be sent and the processServices() returns error code -20.

Example 2 : Change the SOAP incoming request

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 FUNCTION
If 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.

Example 3 : Modify the SOAP outgoing request

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 FUNCTION
If NULL is return from the callback function, a SOAP fault will be sent and the processServices() returns error code -19.
 

Example output

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

Writing your functions

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.

Back to the top