This section explains how to migrate a I4GL web service provider to a Genero application providing the same web service in order to let all clients, already accessing that service, unmodified (excepted for the hostname of course). Notice that the migration will be based on the soa zipcode demo in the I4GL package.
Use the I4GL .4cf configuration file to get all information about the I4GL web service
Example : The I4GL zipcode demo has following .4cf configuration file :
[SERVICE] TYPE = publisher INFORMIXDIR = /dbs/32bits/ifx/11.70.uc2 DATABASE = i4glsoa CLIENT_LOCALE = en_US.8859-1 DB_LOCALE = en_US.8859-1 INFORMIXSERVER = ol_moscou1170uc2 HOSTNAME = moscou.strasbourg.4js.com PORTNO = 9876 I4GLVERSION = 7.50.xC4 WSHOME = /dbs/32bits/ifx/11.70.uc2/AXIS2C WSVERSION = AXIS1.5 TMPDIR = /tmp/zipcodedemo SERVICENAME = ws_zipcode [FUNCTION] NAME = zipcode_details [INPUT] [VARIABLE]NAME = pin TYPE = CHAR(10)[END-VARIABLE] [END-INPUT] [OUTPUT] [VARIABLE]NAME = city TYPE = CHAR(100)[END-VARIABLE] [VARIABLE]NAME = state TYPE = CHAR(100)[END-VARIABLE] [END-OUTPUT] [END-FUNCTION] [DIRECTORY] NAME = /home/f4gl/fg/i4gl FILE = soademo.4gl, [END-DIRECTORY] [END-SERVICE]
Then simply copy your I4GL function without any modification into a new Genero file and add the Genero IMPORT com instruction at the beginning of the file.
Example : The I4GL soa demo contains the zipcode_details service (soademo.4gl)
IMPORT com FUNCTION zipcode_details(pin) DEFINE state_rec RECORD pin CHAR(10), city CHAR(100), state CHAR(100) END RECORD, pin CHAR(10), sel_stmt CHAR(512); LET sel_stmt= "SELECT * FROM statedetails WHERE pin = ?"; PREPARE st_id FROM sel_stmt; DECLARE cur_id CURSOR FOR st_id; OPEN cur_id USING pin; FETCH cur_id INTO state_rec.*; CLOSE cur_id; FREE cur_id; FREE st_id; RETURN state_rec.city, state_rec.state END FUNCTIONNote : you may need some minor code modification for compatibility.
Add a new modular 4GL record where all members map to one of your I4GL web service input parameter, and keep the parameter order as defined in I4gl .4cf file. You must then specify the web service input message name via the Genero XML attribute called XMLName, and assign it to the FUNCTION NAME as defined in the I4GL .4cf file.
Example : In the I4GL zipcode demo there is only one parameter: pin. So add following record at the beginning of the Genero file :
DEFINE zipcode_details_in RECORD ATTRIBUTE(XMLName="zipcode_details") pin CHAR(10) END RECORDNote : Genero web services supports complex datatype as input parameters.
Add another modular 4GL record where all members map to one of your I4GL web service output parameter, and keep the parameter order as defined in I4GL .4cf file. You must then specify the web service output message name via the Genero XML attribute called XMLName, and assign it to the FUNCTION NAME as defined in the I4GL .xcf file concatenated to response.
Example : In the I4GL zipcode demo there are two parameters: city and state. So add following record at the beginning of the Genero file:
DEFINE zipcode_details_out RECORD ATTRIBUTE(XMLName="zipcode_detailsresponse) city CHAR(100), state CHAR(100) END RECORDNote : Genero web services supports complex datatype as output parameters.
Create a Genero 4GL wrapper function without any parameters that will then use the input and output record created at Step 2 and 3 to call the I4GL function passing it the parameters retrieved from the records.
Example : In the I4GL zipcode demo there are 1 input and 2 output parameters. So the 4GL wrapper function must use these records to call the I4GL function as following :
FUNCTION zipcode_details_g() CALL zipcode_details(zipcode_details_in.pin) RETURNING zipcode_details_out.city,zipcode_details_out.state END FUNCTION
Use the COM APIs to publish the I4GL function as a web service based on I4GL .4cf configuration file to get a compatible Genero web service.
To create a new 4GL function in charge of the service publication, you will need following elements of the I4GL .4cf configuration file:
Example : The I4GL zipcode demo has one function published as a Doc/Literal service.
FUNCTION create_zipcode_details_web_service() DEFINE serv com.WebService DEFINE op com.WebOperationNote 1: I4GL supports only Doc/Literal services. Note 2: Genero web services can contain several 4GL functions in the same service. In other words, you can group several I4GL services into the same Genero service.# # Create the web service based on the entries of the .4cf file # SERVICENAME: The name of service is 'ws_zipcode' # FUNCTION NAME: The namespace of the service is built from # the base url 'http://www.ibm.com/' concatenated to # the NAME of the I4GL function 'zipcode_details' #
LET serv = com.WebService.CreateWebService("ws_zipcode","http://www.ibm.com/zipcode_details")# # Create and publish the Doc/Literal web function based on # step 2, step 3 and step 4 # and from the FUNCTION NAME defined in the .4cf file #
LET op = com.WebOperation.CreateDOCStyle("zipcode_details_g","zipcode_details",zipcode_details_in,zipcode_details_out) CALL serv.publishOperation(op,NULL)# # Register the service into the SOAP engine #
CALL com.WebServiceEngine.RegisterService(serv) END FUNCTION
I4GL uses Axis as server for its services, but Genero has its own server programmable via the COM library. Create a new file and add the IMPORT com instruction at beginning of the server file, then simply create the main loop in 4GL that will process any incoming HTTP request.
The port of the service defined in the I4GL .4cf configuration file (via the PORTNO entry) can be reused by setting the FGLAPPSERVER environment variable to the same value before to run the server. However only on development or for tests, on production Genero Web services requires an application server called GAS in charge of load balancing. See the GAS documentation for more details about port configuration for deployment purpose.
Example : To migrate the I4GL zipcode demo, the service must be created in the server before run the main loop as following :
MAIN DEFINE ret INTEGER DEFER INTERRUPTNote : In Genero web service one server can contain several services. In other words, you can put all your I4GL services into one and the same server.# Create zipcode_details service
CALL create_zipcode_details_web_service()# Start the server on port set in FGLAPPSERVER # (to be set to same value as PORTNO defined in the .4cf file)
CALL com.WebServiceEngine.Start()# Handle any incoming request
WHILE TRUE LET ret = com.WebServiceEngine.ProcessServices(-1) CASE ret WHEN 0 DISPLAY "Request processed." WHEN -1 DISPLAY "Timeout reached." WHEN -2 DISPLAY "Disconnected from application server." EXIT PROGRAM# The Application server has closed the connection
WHEN -3 DISPLAY "Client Connection lost." WHEN -4 DISPLAY "Server interrupted with Ctrl-C." WHEN -10 DISPLAY "Internal server error." END CASE IF int_flag<>0 THEN LET int_flag=0 EXIT WHILE END IF END WHILE END MAIN
Based on the DATABASE entry in the I4GL .4cf configuration file, use the Genero instruction to connect to the Informix database at server startup.
Example : In the I4GL zipcode demo the service access the database called : i4glsoa. So add following instruction at the beginning of the server file created as step 6:
DATABASE i4glsoa MAIN ... END MAIN
Then simply compile and link the 2 Genero files created above and run you Genero service. It will be directly available for any client, and provide the WSDL when requested via a HTTP GET with WSDL as query string.
Example : the Genero web service is accessible on URL: http://hostname:9876/ws_zipcode and can even more return the WSDL on URL: http://hostname:9876/ws_zipcode?WSDL.
$ fglcomp -M genero_service.4gl $ fglcomp -M genero_server.4gl $ fgllink -o genero_zipcode genero_service.42m genero_server.42m $ export FGLAPPSEVER=9876 $ fglrun genero_zipcode.42rNote 1 : The hostname depends on the machine your Genero application is started. Note 2 : For deploying the service on production you will need the Genero application server (GAS) to load-balance the service. See the GAS documentation about Web Services when deployment is required.
I4GL is based on Axis web service for the SOAP layer and sends by default requests in MTOM/XOP and with support of WS-Addressing. However, Genero Web services doesn't support MTOM/XOP and WS-Addressing, therefore you have to unset both features on your Axis installation if you still want your I4GL client applications to communicate with the Genero web service after migration.
Example : The Axis installation contains a file called axis.xml where following two lines have to be removed:
<parameter name="enableMTOM" locked="false">true</parameter> <module ref="addressing"/>
This section explains how to migrate a I4GL web service consumer to a Genero application accessing the same web service. Notice that the migration will be based on the soa demo in the I4GL package.
Use the I4GL WSDL located on the Axis server to generate the Genero web service client stub via the tool called fglwsdl.
Example : The WSDL file of the I4GL zipcode demo is located on $INFORMIXDIR/AXIS2C/services/ws_zipcode/zipcode_details.wsdl. So do following command:
$ fglwsdl -noFacets zipcode_details.wsdlIt will generate these two Genero files:
The I4GL WSDL contains namespace declaration for all I4GL web service datatypes, but in practice the I4GL axis server doesn't care about namespaces, but Genero does. So you have to open the generated Genero .inc file and remove all attributes called XMLNamespace and XSTypeNamespace.
Example : The generated .inc file from the I4GL WSDL must be modified as following:
Note : Genero web services provides a lots of XML mapping attributes.#------------------------------------------------------------------------------- # File: ws_zipcode_zipcode_detailsservice.inc # GENERATED BY fglwsdl 101601 #------------------------------------------------------------------------------- # THIS FILE WAS GENERATED. DO NOT MODIFY. #-------------------------------------------------------------------------------
GLOBALS ...# # TYPE : tzipcode_details #
TYPE tzipcode_details RECORD ATTRIBUTE(XMLSequence,XSTypeName="zipcode_details")#,XSTypeNamespace="http://www.ibm.com/zipcode_details")
pin STRING ATTRIBUTE(XMLName="pin") END RECORD#------------------------------------------------------------------------------- # # TYPE : tzipcode_detailsresponse #
TYPE tzipcode_detailsresponse RECORD ATTRIBUTE(XMLSequence,XSTypeName="zipcode_detailsresponse")#,XSTypeNamespace="http://www.ibm.com/zipcode_details")
city STRING ATTRIBUTE(XMLName="city"), state STRING ATTRIBUTE(XMLName="state") END RECORD ...#------------------------------------------------------------------------------- # # Operation: zipcode_details # # FUNCTION: zipcode_details_g() # RETURNING: soapStatus # INPUT: GLOBAL zipcode_details # OUTPUT: GLOBAL zipcode_detailsresponse # # FUNCTION: zipcode_details(p_pin) # RETURNING: soapStatus ,p_city ,p_state # # FUNCTION: zipcode_detailsRequest_g() # RETURNING: soapStatus # INPUT: GLOBAL zipcode_details # # FUNCTION: zipcode_detailsResponse_g() # RETURNING: soapStatus # OUTPUT: GLOBAL zipcode_detailsresponse #
#-------------------------------------------------------------------------------
# VARIABLE : zipcode_details
DEFINE zipcode_details tzipcode_details ATTRIBUTE(XMLName="zipcode_details")#,XMLNamespace="http://www.ibm.com/zipcode_details")
#-------------------------------------------------------------------------------
# VARIABLE : zipcode_detailsresponse
DEFINE zipcode_detailsresponse tzipcode_detailsresponse ATTRIBUTE(XMLName="zipcode_detailsresponse")#,XMLNamespace="http://www.ibm.com/zipcode_details")
END GLOBALS
Add in all I4GL files calling a web service the generated .inc stub with a GLOBALS instruction.
Example : In the I4GL zipcode demo, only the clsoademo.4gl file uses web services. So add following line at beginning of the file :
GLOBALS "ws_zipcode_zipcode_detailsservice.inc" MAIN ... END MAINNote : This allows access to the Genero global variables and datatypes used in the web service call, so as the Genero global wsError record to retrieve error codes if any.
The Genero web service function name is defined in the generated .4gl file and must be used instead of the I4GL function name.
Example : In the I4GL zipcode demo, the web service function name is cons_ws_zipcode and must be renamed to zipcode_details as following:
FUNCTION func_cons_ws_zipcode() DEFINE state_rec RECORD pin CHAR(10), city CHAR(100), state CHAR(100) END RECORD;Note : In Genero web service there is an additional returned parameter : soapstatus. If it contains 0 the operation was a success, otherwise an error occurred.# # Genero web service status returning # whether web function call was successful or not #
DEFINE soapstatus INTEGER# # I4GL web service function name is 'cons_ws_zipcode' # CALL cons_ws_zipcode("97006") returning state_rec.city, state_rec.state
# Genero web service function name is 'zipcode_details'
CALL zipcode_details("97006") returning soapstatus, state_rec.city, state_rec.state ... END FUNCTION
I4GL web service errors are returned on a non conventional SOAP fault what cannot be handled in Genero. However the errors are handled through the additional returned parameter soapstatus that must be checked after each web service call. If its value is not zero, an error has occurred and can be retrieved via the global Genero wsError record defined in the above generated .inc file.
Example : In Genero web service you must check the soap status after each web service call as following:
FUNCTION func_cons_ws_zipcode() DEFINE state_rec RECORD pin CHAR(10), city CHAR(100), state CHAR(100) END RECORD;# # Genero web service status returning # whether web function call was successful or not #
DEFINE soapstatus INTEGER# Genero web service function call
CALL zipcode_details("97006") returning soapstatus, state_rec.city, state_rec.state# Check soap status for errors after zipcode_details call
IF soapstatus<>0 THEN# Display error information from the server
DISPLAY "Error:" DISPLAY " code :",wsError.code DISPLAY " ns :",wsError.codeNS DISPLAY " desc :",wsError.description DISPLAY " actor:",wsError.action ELSE# Display results
DISPLAY "\n ------------------------- \n" DISPLAY "SUPPLIED ZIP CODE: 97006 \n" DISPLAY " ------------------------- \n" DISPLAY "RESPONSE FROM WEB SERVICE \n" DISPLAY " ------------------------- \n" DISPLAY " CITY:",state_rec.city DISPLAY "\n STATE:",state_rec.state DISPLAY "\n ======================== \n" END IF ... END FUNCTION
Then simply compile your modified I4GL application for Genero and execute it. Your application will then connect to the web service passing and returning the parameters as it were only simple 4GL function calls.
Example : To compile your I4GL web service application for Genero you must do following commands:
$ fglcomp -M ws_zipcode_zipcode_detailsservice.4gl $ fglcomp -M clsoademo.4gl $ fgllink -o clsoademo.42r clsoademo.42m ws_zipcode_zipcode_detailsservice.42m $ fglrun clsoademo.42r
I4GL is based on Axis web service for the SOAP layer and sends by default requests in MTOM/XOP and with support of WS-Addressing. However, Genero Web services doesn't support MTOM/XOP and WS-Addressing, therefore you have to unset both features on your Axis installation if you want your Genero client application to communicate with an I4GL web service provider.
Example : The Axis installation contains a file called axis.xml where following two lines have to be removed:
<parameter name="enableMTOM" locked="false">true</parameter> <module ref="addressing"/>
The I4GL standalone axis server adds an extra CR LF after the body of the SOAP HTTP post response what leads the Genero client to return the error message : Body content bigger than expected. This is not allowed as defined in HTTP RFC2616.
Notice however that Axis works as expected if loaded from Apache server.