ORDERSVC

This example program demonstrates how to offer a web service that accepts XML request data, parses the request data into the QORDHDR and QORDDTL tables, then composes and returns XML response data.

The XML composition template file can be found in the second tab - this template is required in order to compose the response XML data. You will need to compile it using CRTRPGTPL.

Because this example is offering a web service, it would need a corresponding RXS Router entry. Here is an example entry:

Where ORD is the library in which the program resides. This library will be added to the top of the library list when the service is called; the RXS library will be added automatically. The URL to access this service with this configuration would be:

[your IBM i address]/orders/ordersvc

Please see this tutorial for more information: Configuring RXS Router.

H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND')

FQORDHDR   IF A E           K DISK    UsrOpn Prefix('H')
FQORDDTL   IF A E           K DISK    UsrOpn Prefix('D')

 /COPY QRPGLECPY,RXSCB
 * This is the first of two times where we need to copy in the template member
 *  for our response XML - it is copied a second time in the ResponseTemplate
 *  subprocedure
 /COPY QRPGLETPL,ORDERTPL

 * ========================================================================
 * Internal prototypes
 * ========================================================================
D RequestHandler  PR
D  pType                        10A   Value
D  pXPath                     1024A   Value Varying
D  pData                          *   Value
D  pDataLen                     10I 0 Value

 * ========================================================================
 * External prototypes
 * ========================================================================

 * ========================================================================
 * Global fields and data structures
 * ========================================================================
D RequestXml      S                   Like(RXS_Var8Kv_t)
D ResponseXml     S                   Like(RXS_Var64Kv_t)
D ParseDS         DS                  LikeDS(RXS_ParseDS_t)
D StdOutDS        DS                  LikeDS(RXS_PutStdOutDS_t)
D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)
D gOrderNumber    S              5P 0
D gInvoiceTotal   S             11P 2
D gItemsCount     S              3P 0
 /FREE
  monitor;
    // Open necessary tables
    if not %Open( QORDHDR );
      open QORDHDR;
    endif;
    if not %Open( QORDDTL );
      open QORDDTL;
    endif;

    // Retrieve request data
    reset RequestXml;
    RequestXml = RXS_GetStdIn();

    // Parse request data and write records
    RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );

    ParseDS.GlobalHandler = %Paddr( RequestHandler );
    RXS_Parse( RequestXml : ParseDS );

    // Compose the response XML
    reset V;
    reset S;

    RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
    ComposeDS.TemplateProcedure = %Paddr( ResponseTemplate );
    RXS_StartComposeEngine( ComposeDS );

    RXS_ComposeVariable( V.OrderNumber : %Char(HORDER) );
    RXS_ComposeVariable( V.OrderDate : %Char(HORDDAT) );
    RXS_ComposeVariable( V.OrderStatus : %Char(HORDSTS) );
    RXS_ComposeVariable( V.CustNum : %Trim( HCUST ) );
    RXS_ComposeVariable( V.ShipVia : %Trim( HSHPVIA ) );
    RXS_ComposeSection( S.ORDER_HEADER );

    RXS_ComposeVariable( V.InvoiceNumber : %Char(HINVNUM) );
    RXS_ComposeVariable( V.ItemCount : %Char(HTOTLIN) );
    RXS_ComposeVariable( V.Amount : %Char(HORDAMT) );
    RXS_ComposeSection( S.INVOICE_HEADER );

    read ORDDTL;
    dow not %Eof();
      if DORDER = HORDER;
        RXS_ComposeVariable( V.ItemNumber : %Char(DPARTNO) );
        RXS_ComposeVariable( V.ItemDesc : %Trim(DPARTDESC) );
        RXS_ComposeVariable( V.ItemQuantity : %Char(DQTYORD) );
        RXS_ComposeVariable( V.UnitPrice : %Char(DUNITAMT) );
        RXS_ComposeVariable( V.TotalPrice : %Char(DTOTAMT) );
        RXS_ComposeSection( S.ITEM );
      endif;
      read ORDDTL;
    enddo;

    RXS_ComposeSection( S.INVOICE_FOOTER );

    RXS_ComposeSection( S.ORDER_FOOTER );

    ResponseXml = RXS_GetComposeBuffer();

    // Return the response XML
    RXS_ResetDS( StdOutDS : RXS_DS_TYPE_PUTSTDOUT );

    StdOutDS.HeaderContentType = 'text/xml';
    RXS_PutStdOut( ResponseXml : StdOutDS );
    RXS_JobLog( ResponseXml );

    // Close files
    if %Open( QORDHDR );
      close QORDHDR;
    endif;
    if %Open( QORDDTL );
      close QORDDTL;
    endif;
  on-error;
    exsr teardown;
  endmon;

  return;

  // ----------------------------------------------------------------------
  begsr teardown;
    if %Open( QORDHDR );
      close(E) QORDHDR;
    endif;
    if %Open( QORDDTL );
      close(E) QORDDTL;
    endif;
  endsr;
 /END-FREE

 * ========================================================================
 * Internal subprocedures
 * ========================================================================
 * ========================================================================
 * Request XML parsing handler
 * ========================================================================
 * This subprocedure handles XML events that are detected by the parser.
 * The XPath structures for the different events are:
 *   '/element>' - element start
 *   '/element/>' - element end
 *   '/element/' - element content
 *   '/element@attribute' - attribute content
P RequestHandler  B
D                 PI
D  pType                        10A   Value
D  pXPath                     1024A   Value Varying
D  pData                          *   Value
D  pDataLen                     10I 0 Value
 /FREE
  RXS_JobLog( pXPath );
  select;
    // This xpath ends in a ">", which means that is it an "Element Begin"
    //  parsing event. These types of events are generally a good trigger
    //  to perform intialization of records or data structures, or reset
    //  counters, etc.
    // Note that "Element Begin" events DO NOT have data.
    when pXPath = '/Order>';
      // At the start of the XML document, reset the QORDHDR record and
      //  zero the invoice total field
      reset ORDHDR;
      gInvoiceTotal = 0;
      gItemsCount = 0;

    when pXPath = '/Order/Items/Item>';
      reset ORDDTL;

    // This xpath ends in a "/>", which is an "Element End" parsing event.
    //  These events are a good place to perform record write or cleanup
    //  operations.
    // Note that "Element End" events DO NO have data.
    when pXPath = '/Order/>';
      HORDAMT = gInvoiceTotal;
      HTOTLIN = gItemsCount;
      HORDSTS = 1;
      HORDDAT = %Dec( %Char(%Date():*YMD0) : 6 : 0 );
      HINVNUM = HORDER + 1;
      write ORDHDR;

    // We will use this event to calculate the line item total cost
    //  and add it to the invoice total, as well as write the line item
    //  record to the table. We also incremement the item count.
    when pXPath = '/Order/Items/Item/>';
      DORDER = HORDER;
      DTOTAMT = DUNITAMT * DQTYORD;
      gInvoiceTotal += DTOTAMT;
      gItemsCount += 1;
      write ORDDTL;

    // This xpath is searching for an "Attribute Content" parsing event,
    //  which is indicated by the '@' symbol before the attribute name.
    // Note that "Attribute Content" events DO have data.
    when pXPath = '/Order@Number';
      HORDER = %Dec( RXS_STR( pData : pDataLen ) : 5 : 0 );

    // This xpath ends in a "/", which is an "Element Content" parsing event.
    //  These events are the main thing you test for when writing an event
    //  handler subprocedure, because this is how you parse content from an
    //  XML document.
    // Note that "Element Content" events DO have data.
    when pXPath = '/Order/Customer/';
      HCUST = RXS_STR( pData : pDataLen );

    when pXPath = '/Order/Ship/';
      HSHPVIA = RXS_STR( pData : pDataLen );
      // Parsed data is retrieved with RXS_STR().
      // Because content is retrieved as character data, we need to
      //  convert the value into the appropriate data type before
      //  writing it to the table.
    when pXPath = '/Order/Items/Item/Number/';
      DPARTNO = %Dec( RXS_STR( pData : pDataLen ) : 6 : 0 );

    when pXPath = '/Order/Items/Item/Description/';
      DPARTDESC = RXS_STR( pData : pDataLen );

    when pXPath = '/Order/Items/Item/Quantity/';
      DQTYORD = %Dec( RXS_STR( pData : pDataLen ) : 3 : 0 );

    when pXPath = '/Order/Items/Item/UnitPrice/';
      DUNITAMT = %Dec( RXS_STR( pData : pDataLen ) : 7 : 2 );

  endsl;

  return;
 /END-FREE
P                 E

 * ========================================================================
 * Response XML composition template subprocedure
 * ========================================================================
P ResponseTemplate...
P                 B
D                 PI
D  p                                  Like(RXS_TEMPLATE_PARM)
   // Templates are created via a two-step process using BLDTPL and
   //  CRTRPGTPL - full walkthrough can be found at
   // https://isupport.katointegrations.com/rxs/latest/creating_an_rxs_template/

   // The RPG Template source is copied from QRPGLETPL by using
   //  /COPY once in the main program D-specs and again below.

 /COPY QRPGLETPL,ORDERTPL
P                 E
::ORDER_HEADER
<Order Number=".:OrderNumber:." Date=".:OrderDate:.">
  <Status>.:OrderStatus:.</Status>
  <Customer>.:CustNum:.</Customer>
  <Ship>.:ShipVia:.</Ship>
::INVOICE_HEADER
  <Invoice Number=".:InvoiceNumber:." ItemCount=".:ItemCount:.">
    <Amount>.:Amount:.</Amount>
    <Items>
::ITEM
      <Item>
        <ItemNumber>.:ItemNumber:.</ItemNumber>
        <Description>.:ItemDesc:.</Description>
        <Quantity>.:ItemQuantity:.</Quantity>
        <UnitPrice>.:UnitPrice:.</UnitPrice>
        <TotalPrice>.:TotalPrice:.</TotalPrice>
      </Item>
::INVOICE_FOOTER
    </Items>
  </Invoice>
::ORDER_FOOTER
</Order>