Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
GrahamRobbo
Active Contributor

In this blog I will describe a way of leveraging ABAP web services that allows you to deliver the response payload through new channels. More specifically I will describe how to construct the web service response payload without having to make SOAP calls.

To better describe this let me start with the business requirements that led to this development.

My customer is running ECC6.0 including a bespoke "Customer Portal" primarily built using the Business Server Page (BSP) programming model.

Via this portal users can track orders and also download and print order confirmations and invoices in Adobe Acrobat format.

There is a new requirement for users to be able to download invoices in text format for subsequent importing into their back-end system.

It doesn't take too much imagination to realise that this quite simple requirement will almost certainly morph into the need for an Application-to-Application solution to replace cumbersome file handling. Therefore we chose to implement using ABAP web services.

If you are comfortable with creating and publishing web services in ABAP you might like to jump past the next section of this blog.

Wrapper function module

There is a SAP delivered function module called BAPI_WEBINVOICE_GETDETAIL which perfectly satisfies our requirements. It not only returns the complete invoice details but also all the associated texts such as company name, payment terms, business partner names and addresses, etc.

This function module uses TABLES interface parameters to pass internal table data to and from the caller. TABLES interface parameters need to be defined in both the input (request) and output (response) portion of a web service interface. This complicates the interface for the consumer because these data structures are only required in the response part of the interface.

To simplify the web services interface I built a wrapper function module around the BAPI call. First I created two table type data dictionary objects.


      ZWEBINVOICEITEM_TAB    with line type BAPIWEBINVITEM


      ZWEBINVOICEPARTNER_TAB with line type BAPIWEBINVPART


Then I created my wrapper function module which has to be RFC-enabled or you won't be able to publish it as a web service.


FUNCTION zbapi_webinvoice_getdetail.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     VALUE(PARTNER_NUMBER) TYPE  VRKPA-KUNDE
*"     VALUE(PARTNER_ROLE) TYPE  VRKPA-PARVW DEFAULT 'AG'
*"     VALUE(BILLING_DOC) TYPE  VBRK-VBELN
*"     VALUE(LANGU) TYPE  SY-LANGU DEFAULT SY-LANGU
*"  EXPORTING
*"     VALUE(WEB_INVOICE_DOCUMENT) TYPE  BAPIWEBINVHEAD
*"     VALUE(RETURN) TYPE  BAPIRET2
*"     VALUE(WEB_INVOICE_ITEMS) TYPE  ZWEBINVOICEITEM_TAB
*"     VALUE(WEB_INVOICE_PARTNERS) TYPE  ZWEBINVOICEPARTNER_TAB
*"----------------------------------------------------------------------




  CALL FUNCTION 'BAPI_WEBINVOICE_GETDETAIL'
    EXPORTING
      partner_number     = partner_number
      partner_role       = partner_role
      billingdoc         = billing_doc
      langu              = langu
    IMPORTING
      webinvoicedocument = web_invoice_document
      return             = return
    TABLES
      webinvoiceitems    = web_invoice_items
      webinvoicepartners = web_invoice_partners.



ENDFUNCTION.


Create Web Service Definition

Now we create a web service definition and deploy the web service. To do this we do not need to perform any programming at all - there is a wizard that does all the work for us. You can find all the details about it in SAP Help under Web Services. This wizard can help you to create web service definitions for function modules, function groups, BAPI's or PI messages.

From the Object Navigator (SE80), in the context menu choose Create -> Enterprise Service / Web Service -> Web Service.

This starts the wizard which then takes you through the process of creating and publishing the web service.

The first screen asks you what sort of object type we are creating. For our example we select "Service Provider".

On the "Service Provider" screen we select "Existing ABAP Objects (Inside Out)" as we are creating a service definition for an existing function module.

The "Provide Service Definition details" screen asks us to give the web service a name, a description and then we select "Function Module" as our endpoint. Let's call our web service 'ZWEBINVOICE_GETDETAIL'.

On the "Choose Endpoint" screen we enter our function module name (ZBAPI_WEBINVOICE_GETDETAIL) and check the "Mapping der Namen" checkbox.

Google Translate tells me that "Mapping der Namen" means "Mapping of Names" in English. This means, for example, that the interface name WEB_INVOICE_ITEMS will be mapped to WebInvoiceItems.

On the "Configure Service" screen select the PRF_DT_IF_SEC_LOW security profile. Check the "Deploy Service" checkbox. If you do not check this box you will need to use the SOAMANGER WebDynpro transaction to create an endpoint for the web service manually.

On the "Enter Package/Request" screen we select the package and transport request, or select "Local Object" and press continue. Selecting "Complete" on the final screen creates the web service definition and deploys it.

Now we should have a working web service.

I like to test my web service at this stage to satisfy myself that all is in order. You can use any SOAP client to do this - maybe even write a test harness in ABAP. Personally I like the SOAP test client included with the XMLSpy tool from Altova.

Directly create XML payload

Now we have built and successfully tested the web service, we return to the original customer requirement - and the main subject of this blog.

While our web service satisfies the perceived future requirements we still need the users to be able to download the XML data using their web browser.

It is possible to have the users' web browser simulate a SOAP call to the web service and grab the response. Similarly we could build ABAP code that makes the SOAP call and return the SOAP response to the user. I rejected both of these options because it seemed to me they added layers of unnecessary complexity. Also I didn't like the fact that the SOAP response would need to be massaged to remove the SOAP Envelope and leave just the contents of the response body - i.e. the "payload".

While looking at the web service definition that was generated by the wizard I realised that, amongst other things, it generated a series of Simple Transformation programs.

Simple transformation programs use a SAP proprietary language to transform ABAP data to XML (serialisation) and from XML to ABAP data (deserialisation). They work, and are invoked, in a similar way to XSL transformations but only have a subset of XSL capabilities. They are, however, much more efficient.

You can find out more about Simple Transformations in the SAP Help.

So if we take the output from the wrapper function module call and serialise it by calling the appropriate Simple Transformation we will have our XML payload.

We need to knwo the name of the appropriate Simple Transformation program to call. This is stored in the database table VEPFUNCST. The primary key of this table consists of the service definition name and the function module name.

Now it is just a matter of building the ABAP code that will return the serialised output from the call to the function module.

I created a BSP page called invoice.xml with two page attributes.


      docnum TYPE VBELN


      kunnr  TYPE KUNNR


Both of these page attributes require the Automatic Page Attribute checkbox selected as these parameters are passed as part of the HTTP request.

In the OnInitialization event handler we code the call to the function module, then query the database for the Simple Transformation to use, call the Simple Transformation, and finally load up the HTTP response object with the resulting XML.


DATA: ls_web_invoice_document  TYPE bapiwebinvhead,
      ls_return                TYPE bapiret2,
      lt_web_invoice_items     TYPE zwebinvoiceitem_tab,
      lt_web_invoice_partners  TYPE zwebinvoicepartner_tab,
      lv_st_name               TYPE st_name,
      xml_string               TYPE string,
      cx_st_error              TYPE REF TO cx_st_error,
      lv_err                   TYPE string,
      lv_attachname            TYPE string.



* Call web service function module

CALL FUNCTION 'ZBAPI_WEBINVOICE_GETDETAIL'
  EXPORTING
    partner_number             = kunnr
*   PARTNER_ROLE               = 'AG'
    billing_doc                = docnum
*   LANGU                      = SY-LANGU
IMPORTING
   web_invoice_document       = ls_web_invoice_document
   return                     = ls_return
   web_invoice_items          = lt_web_invoice_items
   web_invoice_partners       = lt_web_invoice_partners.




TRY.
* Get simple transformation name 
    SELECT SINGLE st_name FROM vepfuncst
      INTO lv_st_name
      WHERE vepname = 'ZWEBINVOICE_GETDETAIL'
    AND function = 'ZBAPI_WEBINVOICE_GETDETAIL'.



* Serialise ABAP data structures

    CALL TRANSFORMATION (lv_st_name)
      SOURCE
         web_invoice_document       = ls_web_invoice_document
         return                     = ls_return
         web_invoice_items          = lt_web_invoice_items
         web_invoice_partners       = lt_web_invoice_partners
         _web_invoice_document      = abap_true
         _return                    = abap_true
         _web_invoice_items         = abap_true
         _web_invoice_partners      = abap_true
      RESULT XML xml_string
      OPTIONS xml_header          = 'full'
              initial_components  = 'include'.




  CATCH cx_st_error INTO cx_st_error.
    lv_err = cx_st_error->get_text( ).

ENDTRY.




* Construct HTTP response


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


* Set content type to XML
response->set_header_field( name  = 'content-type'
                            value = 'text/xml' ).

* do not cache result
response->set_header_field(
                   name  = 'cache-control'
                   value = 'max-age=0' ).

* Set content disposition to attachment
CONCATENATE `attachment; filename=` docnum `.xml` INTO lv_attachname.
response->set_header_field(
                   name  = 'content-disposition'
                   value = lv_attachname ).


* Put XML into HTTP response payload

response->set_cdata( data = xml_string ).


* Inform ICF that response processing is complete

navigation->response_complete( ).


Note how the simple transformation does not just require you to pass the ABAP data structures to it, but also a flag for each one to tell it to process it.

You can see that there is minimal error handling. This is because any error messages from the function module call are also serialised in exactly the same way as if I had called the web service using SOAP.

To test the BSP page and get invoice details returned you need to append the parameters to the URL, like this...


    http://hostname:port/sap/bc/bsp/sap/ztest/invoice.xml?kunnr=0000012345&docnum=0900000010


Of course you can just as easily deliver the XML payload in other ways. Maybe as a text file on the SAPGUI presentation server or send it as an email attachment.

8 Comments