Overview
For some years SAP NetWeaver Gateway has been adding the capability to ABAP based SAP systems to provide REST-based services following the OData standard. This capability enables the much simpler usage of non-SAP technologies like HTML5, mobile or, for example Microsoft SharePoint as a user interface for the rich content provided by SAP systems.
OData is a specific REST-based protocol that provides a lot of advantages (taken from http://www.odata.org):
- Uniform way to expose, structure, query and manipulate data
- Uniform way to represent metadata about the data, allowing computers to know more about the type system, relationships and structure of the data
Not all data we see today is already exposed via OData. This directory shows a list of available REST APIs - and you can see how rapidly the list is growing: http://www.programmableweb.com/apis/directory/1?protocol=REST&sort=date
In addition there are many REST-based industry standards established, for example CMIS or OSLC (http://en.wikipedia.org/wiki/CMIS or http://open-services.net/). For these standards there is typically already a solid ecosystem established.
On the one side we have a powerful and well established tool to create services on top of the SAP Business Suite – SAP NetWeaver Gateway. On the other side there is the requirement to connect the rich data managed inside the SAP Business Suite by using different standards than OData. So what we then did was to leverage the majority of the capabilities of SAP NetWeaver Gateway, and in addition, enable rendering in different formats.
SAP NetWeaver Gateway supports the creation of OData services in the Gateway Service Builder transaction (SEGW) for the many available sources of information available in the SAP Business Suite, SAP Business Warehouse or SAP HANA. The following graphic shows clearly the many possible approaches for defining and implementing the services (http://help.sap.com/saphelp_gateway20sp08/helpdata/en/92/56b605d2b94477a4bab2b3d574cc67/content.htm?frameset=/en/56/d0cc05b564411e841141f68294e29f/frameset.htm).
With additional classes that have now become available in SAP NetWeaver Gateway 2.0 SP08, we provide additional capabilities to make use of these data sources when implementing your own REST based service. The class /IWFND/CL_MGW_FAC_FACADE provides, via its various methods, the support to create, read, update and delete entities, to get entitysets and to execute actions.
In the standard delivery of SAP NetWeaver Gateway 2.0, SP08 you will not find the mentioned standards like CMIS or OSLC supported out-of-the-box. But as a partner or customer these new classes could be the basis for your own implementation.
The architecture for the REST enablement requires a separate ICF node as a starting point. From there a REST handler is calling the rest transformation and the OData runtime via the newly available APIs.
Example
This exemplary use case should help to explain the basics better. Of course the intention of the example is only to convey the basic principles; many other aspects like proper error handling or updating are not covered. In the example, product information from the backend should be provided in a very easy way via a simple REST-based interface. http://server:port/test/rest/Products/ProductDetail?ProductID=xyz should then return the requested information like name, description, height, unit, price, and currency.
The following three steps are required for this:
- Create in the Internet Communication Framework (ICF) a new node for the new REST service
- Create a handler class that is dispatches the calls to their implementations
- Create the implementation classes where the calls are answered
Internet Communication Framework
First we have to create a new node in the ICF Manager (transaction SICF). In order to not confuse the new service with other services, we are creating a new root folder called ‘test’.
In the maintenance dialogue of the service, the settings regarding authentication can be entered. In addition in the tab ‘Handler List’ the class from step 2 (Handler Class) has to be entered. In this example it is called ZMB_REST_EPM_PRODUCT_HANDLER. Please wait with the saving of the ICF Service until the class from step 2 is activated.
Please do not add the new node below /sap/opu/odata, as here already a handler class is entered, which is inherited to other elements from this tree.
Handler Class
When creating the new handler class (in this example ZMB_REST_EPM_PRODUCT_HANDLER) we first have to add in the ‘Properties’ -> ‘Superclass’ the class ‘/IWCOR/CL_REST_HTTP_HANDLER’. With this we get the required interfaces, attributes or methods. In the method ‘GET_ROOT_HANLDER’ we now have to implement our own logic via redefinition.
In this small example we would like to react to ‘ProductDetail’. So in the attached exemplary code (-> Appendix) you will find a mapping of ‘ProductDetail’ to the implementation class from step 3 (Implementation Class). The handler class is also the place where we could add many more requests easily like ‘ProductPrice’, ‘ProductImage’ or ‘ProductEverything’. They could then point to different implementation classes.
Implementation Class
We have now created the required node in the ICF, created the handler class and now we need to perform the third step, the creation of the implementation class to react to the request.
In the example we created a class with the name ‘ZMB_REST_EPM_PRODUCT_IMPLEMENT’. As superclass please enter ‘/IWCOR/CL_REST_RESOURCE’. By doing this you will get the methods for GET, POST, PUT, … and other required methods. Now depending on what we would like to achieve we have to implement (redefine) the corresponding method. In this small example we only need to read information from the backend. So implementing the method for ‘GET’ is sufficient. Otherwise (for deletion, creation or updating) we would also have to implement (redefine) DELETE, POST or PUT.
In the redefinition of ‘/IWCOR/IF_REST_RESOURCE~GET’ in the first block, we fetch the parameters from the query. Of course we need to have the identifier for the product (ProductID). Additionally you will find in the example a query parameter to define the rendering of the result, XML or JSON ($format).
In the next block we read the data from the backend. To ensure controlled access to the backend we are leveraging the Gateway Demo Service explained in this blog: http://scn.sap.com/docs/DOC-44073. As explained earlier we are now taking advantage of all the functionality provided by Gateway Service Builder in order to have the data from the backend available via a clearly defined interface. So we call the internal representation of an existing Gateway service, in this case ‘ZGWSAMPLE_SRV’ with the version ‘0001’. The product data is available in the entity ‘ProductCollection’. This service does not get its data from the SAP Business Suite, but rather uses the demo data (EPM model). So if you want to test this example on a pure SAP NetWeaver Installation without the SAP Business Suite this will still be possible.
In the next step, we are now mapping the result of this query that we received in an internal table to the output structure and are either rendering it in XML or in JSON.
This is all we needed to do. As a result we now receive the requested information from the service.
The XML output is best viewed in Google Chrome, in order to enable JSON output please make sure you have an up-to-date SAP NetWeaver Kernel patch.
Error Handling
We just saw how easy it is to add additional formats based on SAP NetWeaver Gateway. In this section, the additional advantages of using SAP NetWeaver Gateway are explained.
SAP NetWeaver Gateway Client is a powerful tool that helps during the development and testing of the new service – even if it is not an OData service. The Gateway Client provides support for test-case management, analysis of HTTP responses, easy creation of HTTP requests, and much more.
The power of the tool becomes even more visible in the case of error hunting. To simulate this we are now changing in the class ‘ZMB_REST_EPM_PRODUCT_IMPLEMENT’ the key field for the service call from:
* Build the key for the query ls_key_tab-name = 'PRODUCT_ID'. ls_key_tab-value = lv_prod_id. APPEND ls_key_tab TO lt_key_tab.
to
* Build the key for the query ls_key_tab-name = 'PRODUCT_ID12345'. ls_key_tab-value = lv_prod_id. APPEND ls_key_tab TO lt_key_tab.
Of course with this wrong key the system will not be able to return the right attributes. Now in the Gateway Client in the Error Log (and here in the Backend Monitor) the system complains that this key parameter is not available. In addition, we can directly navigate from here to the source code via the call stack where the error occurred. This massively simplifies the time spent with error hunting.
Summary
As of SAP NetWeaver Gateway 2.0 SP08, we offer an additional feature that permits better interactions from external sources with the SAP Business Suite. The combination of the advantages of SAP NetWeaver Gateway (structured and controlled access to the SAP Business Suite and powerful testing / logging / tracing tools) with the flexibility to implement additional REST-based standards should enable additional scenarios that were hard to implement and maintain / monitor before.
Appendix
Handler Class
method /IWCOR/IF_REST_APPLICATION~GET_ROOT_HANDLER. DATA: lo_router TYPE REF TO /iwcor/cl_rest_router. IF mo_server IS BOUND. CREATE OBJECT lo_router. lo_router->attach( iv_template = '/ProductDetail' iv_handler_class = 'ZMB_REST_EPM_PRODUCT_IMPLEMENT' ). lo_router->attach( iv_template = '/ProductDetail/' iv_handler_class = 'ZMB_REST_EPM_PRODUCT_IMPLEMENT' ). ro_root_handler = lo_router. ENDIF. endmethod.
Implementation Class - Get
METHOD /iwcor/if_rest_resource~get. TYPES: BEGIN OF ltp_output, myname TYPE string, mydescription TYPE string, myheight TYPE decfloat34, myunit TYPE c LENGTH 1, myprice TYPE decfloat34, mycurr TYPE c LENGTH 3, END OF ltp_output. DATA: lr_entity TYPE REF TO data, lv_errorflag TYPE c LENGTH 1, lx_busi_exception TYPE REF TO /iwfnd/cx_mgw_busi_exception, lx_tech_exception TYPE REF TO /iwfnd/cx_mgw_tech_exception, lv_current_time TYPE timestamp, lv_txt TYPE string, lv_json TYPE xstring, lo_entity TYPE REF TO /iwcor/if_rest_entity, writer TYPE REF TO cl_sxml_string_writer, ls_key_tab TYPE /iwfnd/s_mgw_name_value_pair, lt_key_tab TYPE TABLE OF /iwfnd/s_mgw_name_value_pair, ls_output TYPE ltp_output, lt_output TYPE TABLE OF ltp_output, lv_prod_id TYPE string, lv_format TYPE string. FIELD-SYMBOLS: <l_name> TYPE any, <l_unit> TYPE any, <l_price> TYPE any, <l_height> TYPE any, <l_descr> TYPE any, <l_curr> TYPE any, <ft_output> TYPE ANY TABLE, <fs_output> TYPE any. GET TIME STAMP FIELD lv_current_time. * Get the input parameters from the query lv_prod_id = mo_request->get_uri_query_parameter( iv_name = 'ProductID' ). lv_format = mo_request->get_uri_query_parameter( iv_name = '$format' ). * Build the key for the query ls_key_tab-name = 'PRODUCT_ID'. ls_key_tab-value = lv_prod_id. APPEND ls_key_tab TO lt_key_tab. TRY. * Do the query CALL METHOD /iwfnd/cl_mgw_fac_facade=>get_entity EXPORTING iv_service_name = 'ZGWSAMPLE_SRV' iv_version = '0001' iv_entity_set_name = 'ProductCollection' it_key_tab = lt_key_tab io_rest_request = me->mo_request IMPORTING er_entity = lr_entity. CATCH /iwfnd/cx_mgw_busi_exception INTO lx_busi_exception. lv_errorflag = abap_true. CATCH /iwfnd/cx_mgw_tech_exception INTO lx_tech_exception. lv_errorflag = abap_true. ENDTRY. * Prepare Output Stream lo_entity = mo_response->create_entity( ). lo_entity->set_modification_date( iv_modification_date = lv_current_time ). lo_entity->set_content_compression( abap_true ). * Everything ok for outputting? IF lr_entity IS NOT INITIAL AND lv_errorflag IS INITIAL. * Map the results of the query to the output structure ASSIGN lr_entity->* TO <fs_output>. ASSIGN COMPONENT: 'NAME' OF STRUCTURE <fs_output> TO <l_name>, 'DESCRIPTION' OF STRUCTURE <fs_output> TO <l_descr>, 'DIM_UNIT' OF STRUCTURE <fs_output> TO <l_unit>, 'PRICE' OF STRUCTURE <fs_output> TO <l_price>, 'CURRENCY_CODE' OF STRUCTURE <fs_output> TO <l_curr>, 'HEIGHT' OF STRUCTURE <fs_output> TO <l_height>. MOVE: <l_name> TO ls_output-myname, <l_descr> TO ls_output-mydescription, <l_unit> TO ls_output-myunit, <l_curr> TO ls_output-mycurr, <l_price> TO ls_output-myprice, <l_height> TO ls_output-myheight. * in xml or json? IF lv_format EQ 'json'. writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ). * transform to JSON CALL TRANSFORMATION id SOURCE proddata = ls_output RESULT XML writer. lv_json = writer->get_output( ). lo_entity->set_content_type( iv_media_type = 'application/json' ). lo_entity->set_binary_data( lv_json ). ELSE. * XML Transformation CALL TRANSFORMATION id SOURCE proddata = ls_output RESULT XML lv_txt. lo_entity->set_content_type( iv_media_type = 'application/xml' ). lo_entity->set_string_data( lv_txt ). ENDIF. * finalize the output mo_response->set_status( /iwcor/cl_rest_status_code=>gc_success_ok ). ELSE. * error occured lo_entity->set_string_data( 'Error occured' ). lo_entity->set_content_type( iv_media_type = 'text/plain' ). mo_response->set_status( /iwcor/cl_rest_status_code=>gc_client_error_bad_request ). ENDIF. ENDMETHOD.
Implementation Class – Constructor
method CONSTRUCTOR. super->constructor( ). endmethod.