Submitted by ppk on Thu, 09/13/2012 - 10:20
Forums

Hi

Apologies for the noob question.

There are two ways that I know of to create a web service that provides an XML response:

  1. create a .asmx web service that defines a procedure whose input and output parameters are the input and output data elements in the XML
  2. use ILOBs in a .rpgle program

Option 1 is great because I can easily use something like soapUI to test the web service. Am I right in thinking that I can't use soapUI to test ILOB web services?

Also, as far as I understand it, the .asmx option is only good for "flat" XML requests where the input parameters are all at the same level (sorry if I'm not using correct terminology). For example, if my XML request includes data several levels deep, like:

<Order>
  <Header Orderno='436533' Custname='John Doe'>
  <Detail>
    <Line lineno='1' itemno='4711' description='Nails 7"' qty='100'/>
    <Line lineno='2' itemno='3214' description='Bolts 2"' qty='50'/>
  </Detail>
<Order>

... then I would need to use an ILOB in a .rpgle program to process that request? Am I right in thinking I can't use a straightforward .asmx web service that defines a procedure to do it?

 

Thanks for listening!

Niels Liisberg

Thu, 09/13/2012 - 14:00

First - it is not at all a noob question...cool

Basically .asmx or .aspx determins if you want to expose a service program or just a program. The big diffrence is that service programs (aka. .asmx) have one or many procedures you can call where as the program only have one entry point.

If you comprare these therms in the SOAP word - "sevices and methods" that maps quite well to "service programs and procedures" So that is the big reason for using .asmx with soap: The SOAP service metod are being mapped to the service programs procedure with the same name - All done by IceBreak.

So you can have as many "methods" aka. "procedures" in you service as you like. When you compile the .asmx in IceBreak it will automatically produce the WSDL - the WebService Description Language - file as a response if you enter the ?WSDL in the browser URL like:

   http://myIBMI/myService.asmx?WSDL

This returns you WSDL description - which also are used by SoapUI.

Also notice that IceBreak .asmx SOAP services does not allow you to interact directly with the response object, where "normal" .aspx programs does allow you to build the response. 

Now - ILOB's (Internal Large OBjects) - If you just want to send Plain Old XML (aka POX -services) back and forth between the client and the IceBreak server then ILOB's are not required but can be of grat help some times. Also - you don't need multiple entry points, so a normal .aspx or .rpgle will do the trick.

And most important: To answer your question above: If you are receiving the XML in a IceBreak program , then icebreak will automatially parse the HTTP contents with the build in IceBreak-XML parser. The contents need some kind MIME extention of XML. So if your client can do something similar to :

httpReq.SetHeader('Content-Type', 'application/xml') ;

    httpReq.Send('<myxml><mytag myparm="an exampl"/></myxml>";

The you can simply use it directly in the IceBreak Progam ( note the "x-path" notation):

myParm = ReqXmlGetValue('/myxml/mytag@myparm');

    ... and myParm will have the value of "an example

Or if you need the full features from the xmlparser, the just the the pointer to the xml object:

 

myXmlPtr =  ReqXmlPtr();

    myParm = Xml_GetValue(myXmlPtr : '/myxml/mytag@myparm': 'Default value goes here');
And this can be done from a simple RPGLE .aspx program you dont need it to be a service program / .asmx program
 
When sending the XML back to the client you just produce the XML response like in this small RPGLE IceBreak .aspx program:
<%
setContentsType('application/XML;charset=UTF-8');
*INLR =*ON;
%>
<myxml>
  <mytag parm="This is my reponse" timeis="<%= %char(%timestamp)) %>"/>
</myxml>
 
 

    

 

   

 

 

Hi Niels

Thanks very much for your reply. I had to read it a few times, but it's going in smiley

We have a client who will be sending us XML like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>

Presumably, I would need a .rpgle to process this, and not a .asmx? I can't see how a procedure in a .asmx could cope with that kind of structured data. I'll confess: I have already coded a .rpgle that seems to work, I just wanted to check that I wasn't doing this in a cockeyed kind of way.

I'm using reqXmlGetValue and the x-path to extract the data. Will that work on any inbound XML, regardless of whether it's via an ILOB (like my test XML sender) or any other method?

Muchas gracias.

Hi ppk;

The question here is: Is this payload wraped in a SOAP request. If not, ie. this is the complete http payload, which i presume - then it is a Plain Old XML (POX service)

For POX services it is always a .ASPX program you will need: You have only one program entry point and the payload can have any form and shape it like - no validation scheme - just plain old XML.

To make a POX service your can use the folloing code snippet:

 

     d pXml            s               *

     d pVisit          s               *

     d pConsignment    s               *

     d msg             s             50    varying

     d xpath                     128    varying

     d i               s             10i 0

     d manifestId      s             10i 0

     d visitId         s             10    varying

     d visitSeq        s             10i 0

     d arrival         s               Z

     d consignments    s             10i 0

     d orderRef        s             10i 0

     d customerCode    s             10    varying

     d discrepancy     s               n

      /include qasphdr,xmlparser

      /free

        // First parse the XML stream

        pXml = ReqXmlPtr();

        if Xml_Error(pXml) ;

           msg = xml_Message(pXml);

           xml_Close(pXml);

           return;

        endif;

 

        // Get the manifest id: that is a attribue on the root hench the @

        manifestId = %int(xml_GetValue (pXml : '/manifest@ID':'0'));

 

        // Now locate the "visit" and let it be the new temorary root

        // You can also use a comple reference from the root if you like

        pVisit = xml_locate(pXml:'/manifest/visit');

        if (pVisit =  *NULL);

           // If we did not found the "visit" element we die

           // - remember to close the xml to avoid a leak

           xml_Close(pXml);

           return;

        endif;

 

        // Now extrace the values from the "visit" tag - some are attribues, some are elements

        visitId   = xml_GetValue (pVisit: '@ID');                               // As String

        visitSeq  = %int(xml_getValue(pVisit: '@sequence':'0'));                // As Number

        arrival   = CvtWsTs2Ts(xml_GetValue (pVisit: 'datetimeActualArrival')); // As DB/2 timestamp

 

        // Consignments is an array: use UBOUND to detect the numbers of entries

        consignments = %int(xml_GetValue(pVisit:'consignment[UBOUND]':'0'));

 

        // Now loop for each elemnt consignments. Note - x-path use 0 as the first element

        // Just for the fun we will find the element from the root - an absolute path

        // but from "pVisit" as root and the only the "consignment" and index will work just fine

        for i = 0 to consignments -1;

           xpath = '/manifest/visit/consignment[' + %char(i) + ']';

           pConsignment = xml_locate(pXml: xpath );

           orderRef     = %int(xml_GetValue(pConsignment: 'orderRef':'0'));    // As number

           customerCode = xml_GetValue (pConsignment: 'customerCode');         // As string

           discrepancy  = 'NO' <>  xml_GetValue (pConsignment: 'discrepancy'); // As boolean

        endfor;

        xml_Close(pXml);

        *inlr = *on;

Hi Niels

Every day is a school day.

Amazingly, I managed to code something very similar to what you coded above. Yay. There are a couple of opcodes I've not seen before, though, (ReqXmlPtr and xml_locate) which I like the look of.

I've been told that the inbound XML will be a SOAP request - does that mean I can still use a .rpgle to process it?

 

Hi PPK,

If your interface is SOAP then you will always need at multi-entry-point programs. That will be a service program, and service programs in IceBreak are build with the .ASMX extension.

 

Now- if we wrap your XML from above into SOAP the you will have XML with in XML - which is no problem at all.

What you need to do, however, is to declare your parameter as a  POINTER, IceBreak delivers the pointer to the SOAP xml element itself.

Likewise – when you return an element with XML (or any other kind of large data) to the SOAP client, IceBreak will do the undelaying magic and load the contents to the response from your XML response request XML parameter in an ILOB.  This is where the ILOB fits in.

Let me first explain the last part - How you can send a XML response back to the client - after I will show how to parse the XML input from SOAP:

Producing XML within a SOAP service:

I have pasted a snippet from the manual to show you how to place a XML document provided by the IceBreak build-in SQL to XML provider.

To get you started, simply copy the following code and paste it to your IFS ins one IceBreak server root and name it MYSOAP1.ASMX.

Now compile it with the IceBreak JIT-compiler: Just refer it from SoapUI as a WSDL like

   http://myIBMi/mysoap1.asmx?WSDL

and SOAP service will expose the method "GetPorductList"

Now the snippet from the manual........

ILOB's or Internal Large Objects is heavily used in IceBreak to store large chunks of data. In this example I’ll show you how to run a SQL query based on a request in a WebService. The XML result is placed in an ILOB and returned to the WebService client as a string.

 

 

<%language="RPGLE" pgmtype="webservice" %><%

h NOMAIN

 

/include qasphdr,ilob

 

p*name++++++++++..b...................keywords++++++++++++++++++++++++++comments++++++++++++

p GetProductList...

p                 B                   export

 

d                 PI            

d SqlCmd                      1024    input varying

d xmlout                          *   output

 

d*Name++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++Comments++++++++++++

d Error           s               N

d i               s             10i 0

 

/free

 

xmlout = SesGetILOB('sqllist');     //' The SQL result set will go directly to the ILOB 

setResponseObject ( xmlout );       //' Redirect all output to the response object to the ILOB

ILOB_SetWriteBinary(xmlout : *ON);  //' This is in ASCII so don't x-late it later.

 

 

%><?xml version="1.0" encoding="windows-1252" ?><%

 

   

//' Now run the SQL query and put all data into the response object

MaxRows = 10000; 

Error   = SQL_Execute_XML(sqlcmd : maxrows);

 

if (Error);

    %><error msg="<% = getLastError('*MSGTXT') %>"

             help="<% = getLastError('*HELP') %>"/><%

endif;

 

setResponseObject (*NULL);         //' Go back to use the normal response object from here

 

/end-free

P GetProductList...

P                 E

 

 

%>

 

 

 

As you can see - it is pretty much a normal service program - except for the input/output keywords.  It is a service program with no mainline. It simply exports a procedure. However, You are not restricted to only one.

The trick here is that xmlout is defined as as pointer. When IceBreak recognize a pointer in th WebService declaration it will assume it is a ILOB'pointer. In this example I have used the SQL_Execute_XML() to produce the SQL result set XML by redirecting the ResponseObject to the xmlout ILOB.

Now IceBreak is tranfering the resultset in the WebService interface.

 ....... End Snippet.

The magic is in the declaration of "xmlout" as a pointer. This is an pointer to the ILOB object.

Receiving XML within a SOAP service:

Now - when we are receiving XML, ensure that the clien wraps the xml in CDATA tags so the parser will not be confused by XML in XML. Your SOAPui request has to look similar to the following:

   <soapenv:Header/>
   <soapenv:Body>
      <sys:HandleManifest>
         <!--Optional:-->
         <sys:pManifest><![CDATA[
<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>
]]>
</sys:pManifest>
      </sys:HandleManifest>
   </soapenv:Body>
</soapenv:Envelope>
   <soapenv:Header/>
   <soapenv:Body>
      <sys:HandleManifest>
         <!--Optional:-->
         <sys:pManifest><![CDATA[
<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>
]]>
</sys:pManifest>
      </sys:HandleManifest>
   </soapenv:Body></soapenv:Envelope><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:sys="http://systest.icebreak.org/"&gt;

 

   <soapenv:Header/>
   <soapenv:Body>
      <sys:HandleManifest>
         <!--Optional:-->
         <sys:pManifest><![CDATA[
<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>
]]>
</sys:pManifest>
      </sys:HandleManifest>
   </soapenv:Body>
</soapenv:Envelope><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:sys="http://systest.icebreak.org/"&gt;
   <soapenv:Header/>
   <soapenv:Body>
      <sys:HandleManifest>
         <!--Optional:-->
         <sys:pManifest><![CDATA[
<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>
]]>
</sys:pManifest>
      </sys:HandleManifest>
   </soapenv:Body>
</soapenv:Envelope><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:sys="http://systest.icebreak.org/"&gt;
   <soapenv:Header/>
   <soapenv:Body>
      <sys:HandleManifest>
         <!--Optional:-->
         <sys:pManifest><![CDATA[
<?xml version="1.0" encoding="utf-8"?>
<manifest ID="123456">
  <visit ID="ABCDE" sequence="10">
    <datetimeActualArrival>2011-11-07T09:28:23.520</datetimeActualArrival>
    <datetimeActualDeparture>2011-11-07T09:28:49.550</datetimeActualDeparture>
    <late>LATE</late>
    <callSequence>10</callSequence>
    <consignment type="delivery">
      <orderRef>123123</orderRef>
      <customerCode>CUST1</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="delivery">
      <orderRef>123124</orderRef>
      <customerCode>CUST2</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
    <consignment type="collection">
      <orderRef>123125</orderRef>
      <customerCode>CUST3</customerCode>
      <discrepancy>NO</discrepancy>
    </consignment>
  </visit>
</manifest>
]]>
</sys:pManifest>
      </sys:HandleManifest>
   </soapenv:Body>
</soapenv:Envelope>

 So lets modify your program and append the follwoing code to handle your XML. :

 

 

.... previous code from above....

         setResponseObject (*NULL);         //' Go back to use the normal response object from here

       /end-free

     p GetProductList...

     p                 E

      *-----------------------------------------------------------------------------------------

 

     p HandleManifest...

     p                 B                   export

      

     d                 PI            

     d pManifest                       *   input

     d someResponse                 256    output varying

     d pXml            s               *

     d pVisit          s               *

     d pConsignment    s               *

     d pConsArr        s               *

     d msg             s             50    varying

     d xpath                     128    varying

     d i               s             10i 0

     d manifestId      s             10i 0

     d visitId         s             10    varying

     d visitSeq        s             10i 0

     d arrival         s               Z

     d consignments    s             10i 0

     d orderRef        s             10i 0

     d customerCode    s             10    varying

     d discrepancy     s               n

      /free

        

        // First parse the XML stream

        pXml = Xml_ParseString (pManifest:'');

        if Xml_Error(pXml) ;

           msg = xml_Message(pXml);

           xml_Close(pXml);

           return;

        endif;

 

        // Get the manifest id: that is a attribue on the root hench the @

        manifestId = %int(xml_GetValue (pXml : '/manifest@ID':'0'));

 

        // Not locate the "visit" and let it be the new temorary root

        // You can also use a comple reference from the root if you like

        pVisit = xml_locate(pXml:'/manifest/visit');

        if (pVisit =  *NULL);

           // If we did not found the "visit" element we die - remember to close the xml to avoid 

           xml_Close(pXml);

           return;

        endif;

 

        // Now extrace the values from the "visit" tag - some are attribues, some are elements

        visitId   = xml_GetValue (pVisit: '@ID');                               // As String

        visitSeq  = %int(xml_getValue(pVisit: '@sequence':'0'));                // As Number

        arrival   = CvtWsTs2Ts(xml_GetValue (pVisit: 'datetimeActualArrival')); // As DB/2 timestamp

 

        // Consignments is an array: use UBOUND to detect the numbers of entries

        consignments = %int(xml_GetValue(pVisit:'consignment[UBOUND]':'0'));

 

        // Now loop for each elemnt consignments. Note - x-path use 0 as the first element

        // Just for the fun we will find the element from the root - an absolute path

        // but from "pVisit" as root and the only the "consignment" index will work just fine

        for i = 0 to consignments -1;

           xpath = '/manifest/visit/consignment[' + %char(i) + ']';

           pConsignment = xml_locate(pXml: xpath );

           orderRef     = %int(xml_GetValue(pConsignment: 'orderRef':'0'));    // As number

           customerCode = xml_GetValue (pConsignment: 'customerCode');         // As string

           discrepancy  = 'NO' <>  xml_GetValue (pConsignment: 'discrepancy'); // As boolean

        endfor;

 

 

        xml_Close(pXml);

      

        someResponse = 'Ok from Visit: ' + visitId + ' at: ' + %char(arrival);

      /end-free

     P                 E

%>

 
Basically -this does not differ much from my previous response to you. The only difference is that we receive a pointer to the XML tag directly, so we can easy utilize the IceBreak build-in: Xml_ParseString directly on the sope -xml element. since it works on both strings and pointers to zeroterminated c-strings  which gives you virtually no size limit on the XML document ( Cool!! )
 
Also I send a littele response back to the client to inform that my xml parsing went well
 
You can try the code with SOAP-ui and create a new SOPA-ui project with the soap service wsdl at:
 
  
Send me your email address to support@icebreak.org and ask them to forward it to me, and i can provide you with the source code if you like.

Regards

Niels 

Hi PPK;

My pleasure; 

Some thing you migth think ablut comming in the horizon: What if you XML payload contains another XML - then you have XML in XML in XML - and CDATA in CDATA does not work.

The answer here is to use base64 encoding - but we can talk about that later if you run into troubles...

Again - my plasure to help you. We are always ready to support you.  

Regards

Niels Liisberg