Skip to Content
Technical Articles

How to call a remote OData service from the trial version of SAP Cloud Platform ABAP environment

Updates

  •  21.10. use of more inline declarations and filter to overcome the BT restriction

 

We recently made the trial version of SAP Cloud Platform ABAP Environment availalble as described in my blog It’s Trial Time for ABAP in SAP Cloud Platform.

One of the known limitations is that you can’t use the destination service and it is thus not possible to use http or RFC destinations to communicate to remote systems.

There is however the option to create a destination through providing a URL

data base_url type string.
base_url = '<base url of an OData service>'.
http_client = cl_web_http_client_manager=>create_by_http_destination( i_destination =  cl_http_destination_provider=>create_by_url( i_url = base_url ) ).

    http_client->get_http_request( )->set_authorization_basic(
        i_username = '<username>'
        i_password = '<password>'
    ).

 

!!! Warning !!!

An obvious drawback of this option when being used in a shared trial version is that everybody else can read your code and would thus be able to read your username and password as well.

 

Demo service being used

 

The demo service that I will use is a simple OData service that is published on the SAP Gateway demo system ES5.

https://sapes5.sapdevcenter.com/sap/opu/odata/sap/ZE2E100_SOL_2_SRV/$metadata

 

How to get a user in ES5 is described in my following blog.

 

Creating the Service consumption model

 

We first have to download the $metadata file of the OData service we want to consume. Firefox is a good option.

We start by creating a service consumption model ZSC_SALESORDERDEMO which has to be used by the client proxy that we use in our ABAP code to call the remote OData service by uploading the Service Metadata File that we have just downloaded.

In the next step you are able to select then entity sets of the OData service you want to consume.This option comes very handy since it allows to minimize the number of repository objects (CDS views that contain abstract entities) that are going to be created to the bare minimum needed.

Please note that it is in this step where you can provide another name for the ABAP artifacts that will be generated.

You just have to confirm that the below shown artifacts will be generated.

As a result three repository objects are generated

such as a Service Consumption Model

A Service Definition

@EndUserText.label: 'ZSC_SALESORDERDEMO'
@OData.schema.name: 'ZE2E100_SOL_2_SRV'
define service ZSC_SALESORDERDEMO {
 expose ZSALESORDERDEMO; 
 } 

as well as a Data Definition that contains an abstract entity

/********** GENERATED on 10/19/2019 at 18:25:05 by CB0000000083**************/
 @OData.entitySet.name: 'Zsepm_C_Salesorder_SOL' 
 @OData.entityType.name: 'Zsepm_C_Salesorder_SOLType' 
 define root abstract entity ZSALESORDERDEMO { 
 key SalesOrder : abap.char( 10 ) ; 
 @Semantics.amount.currencyCode: 'TransactionCurrency' 
 NetAmountInTransactionCurrency : abap.dec( 16, 3 ) ; 
 @Semantics.amount.currencyCode: 'TransactionCurrency' 
 TaxAmountInTransactionCurrency : abap.dec( 16, 3 ) ; 
 SalesOrderLifeCycleStatus : abap.char( 1 ) ; 
 SalesOrderBillingStatus : abap.char( 1 ) ; 
 SalesOrderDeliveryStatus : abap.char( 1 ) ; 
 SalesOrderOverallStatus : abap.char( 1 ) ; 
 Opportunity : abap.char( 35 ) ; 
 SalesOrder_Text : abap.char( 255 ) ; 
 CreationDateTime : tzntstmpl ; 
 LastChangedDateTime : tzntstmpl ; 
 IsCreatedByBusinessPartner : abap_boolean ; 
 IsLastChangedByBusinessPartner : abap_boolean ; 
 Customer : abap.char( 10 ) ; 
 @Semantics.currencyCode: true 
 TransactionCurrency : abap.cuky( 5 ) ; 
 @Semantics.amount.currencyCode: 'TransactionCurrency' 
 GrossAmountInTransacCurrency : abap.dec( 16, 3 ) ; 
 
 } 

 

Please note that also sample code is generated to consume your OData service for all CRUD-Q operations.

 

Consuming the service using a simple class

 

I have taken the above code as a starting point and replaced the Hungarian notation prefixes 😉 and tried to provide a sample code that follows the clean ABAP principles.

Any suggestions to improve the code are welcome.

The class has basically two methods

  • get_http_client
  • get_odata_response

The method get_http_client( ) creates an instance of the http_client object using the base URL of our OData service. The http_client object is than used by the method get_odata_response( ) to consume the OData service.

The client proxy needs three parameters:

  •  the name of the consumption model that ZSC_SALESORDERDEMO
  •  the http_client object
  •  the relative URL /sap/opu/odata/sap/ZE2E100_SOL_2_SRV/ that points to the service document of the OData service

Based on the client proxy object another object is created to perform the READ request on the entity set Zsepm_C_Salesorder_SOL. Please note that you have to use the ABAP internal name ZSEPM_C_SALESORDER_SOL here which is in upper case .

In the following a filter tree is created to filter for the key field SalesOrder and the property NetAmountInTransactionCurrency which is of type currency.

Please note that the filter node for the property NetAmountInTransactionCurrency needs the currency provided via the optional parameter iv_currency_code.

If you omit to provide this information you will get the error message:

No currency code supplied for property path ‘NETAMOUNTINTRANSACTIONCURRENCY’.

 

How to create filter statements for intervals

 

In my example I want to retrieve only sales orders with a net amount between $3000 and $7000. To achieve this goal we currently have to create two child nodes that both contain filter options to retrieve sales orders. One node for sales orders with a net amount larger than $3000 and one node for sales orders with a net amount smaller than $7000. This is because currently range tables with the option BT are NOT supported.

Not supported:
range_for_netamounts2 = VALUE #( ( sign = ‘I’ option = ‘BT‘ low = ‘3000’ high = ‘8000’) ).

The code looks like follows:

CLASS zcl_call_odata_from_trial DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
  PROTECTED SECTION.
    DATA salesorders TYPE STANDARD TABLE OF zsalesorderdemo.
  PRIVATE SECTION.

    METHODS get_http_client
      IMPORTING base_url           TYPE string
      RETURNING VALUE(http_client) TYPE REF TO if_web_http_client
      RAISING
                cx_web_http_client_error
                cx_http_dest_provider_error.

    METHODS get_odata_response
      IMPORTING http_client TYPE REF TO if_web_http_client
      EXPORTING entityset   LIKE salesorders
      RAISING
                cx_web_http_client_error
                /iwbep/cx_gateway.

ENDCLASS.



CLASS zcl_call_odata_from_trial IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    DATA base_url TYPE string.
    base_url = 'https://sapes5.sapdevcenter.com/'.

    TRY.
        DATA(http_client) = get_http_client( base_url = base_url ).
      CATCH cx_web_http_client_error cx_http_dest_provider_error INTO DATA(error_creating_http_client).
        out->write( error_creating_http_client->get_longtext(  )  ).
        EXIT.
    ENDTRY.

    TRY.
        get_odata_response(
         EXPORTING http_client = http_client
         IMPORTING entityset = DATA(salesorders) ).
      CATCH cx_web_http_client_error /iwbep/cx_gateway INTO DATA(error_calling_OData_service).
        "handle exception
        out->write( error_calling_OData_service->get_longtext(  )  ).
        EXIT.
    ENDTRY.

    LOOP AT salesorders INTO DATA(salesorder).
      out->write( salesorder ).
    ENDLOOP.

  ENDMETHOD.

  METHOD get_http_client.
    http_client = cl_web_http_client_manager=>create_by_http_destination( i_destination =  cl_http_destination_provider=>create_by_url( i_url = base_url ) ).

    http_client->get_http_request( )->set_authorization_basic(
        i_username = '<your ES5 user>'
        i_password = '<password of your ES5 user>'
    ).
  ENDMETHOD.

  METHOD get_odata_response.

    DATA entity LIKE LINE OF entityset.
    DATA range_for_salesorders LIKE RANGE OF entity-salesorder.
    DATA range_for_salesorders2 LIKE RANGE OF entity-salesorder.
    DATA range_for_netamounts LIKE RANGE OF entity-netamountintransactioncurrency.
    DATA range_for_netamounts2 LIKE RANGE OF entity-netamountintransactioncurrency.
    DATA(client_proxy) = cl_web_odata_client_factory=>create_v2_remote_proxy(
      EXPORTING
        iv_service_definition_name = 'ZSC_SALESORDERDEMO'
        io_http_client             = http_client
        iv_relative_service_root   = '/sap/opu/odata/sap/ZE2E100_SOL_2_SRV/' ).

    DATA(odata_request) = client_proxy->create_resource_for_entity_set( 'ZSEPM_C_SALESORDER_SOL' )->create_request_for_read( ).

    odata_request->set_top( 5 )->set_skip( 0 ).

* filter
    range_for_salesorders = VALUE #(
      ( sign = 'I' option = 'GE' low = '500000100' ) ).
    range_for_netamounts = VALUE #(
      ( sign = 'I' option = 'LE' low = '8000' ) ).
    range_for_netamounts2 = VALUE #(
      ( sign = 'I' option = 'GE' low = '3000' ) ).
    DATA(filter_factory) = odata_request->create_filter_factory( ).
    DATA(filter_child_node_1)  = filter_factory->create_by_range( iv_property_path     = 'SALESORDER'
                                                                  it_range             = range_for_salesorders ).
    DATA(filter_child_node_2)  = filter_factory->create_by_range( iv_property_path     = 'NETAMOUNTINTRANSACTIONCURRENCY'
                                                                  it_range             = range_for_netamounts
                                                                  iv_currency_code     = 'USD' ).
    DATA(filter_child_node_3)  = filter_factory->create_by_range( iv_property_path     = 'NETAMOUNTINTRANSACTIONCURRENCY'
                                                                  it_range             = range_for_netamounts2
                                                                  iv_currency_code     = 'USD' ).
    DATA(filter_root_node) = filter_child_node_1->and( filter_child_node_2->and( filter_child_node_3 ) ).
    odata_request->set_filter( filter_root_node ).

* retrieve business data
    DATA(odata_response) = odata_request->execute( ).
    odata_response->get_business_data( IMPORTING et_business_data = entityset ).

  ENDMETHOD.

ENDCLASS.

 

And if you run it via F9 you get the following result.

All salesorders have a net amount between 3000$ and 8000$ and the Salesorder ID is larger than 500000100 and the result set is limited to the first 5 hits.

 

 

 

 

 

10 Comments
You must be Logged on to comment or reply to a post.
  • No animals nor hungarian notations where harmed in this example. Nice, the hope is still alive 🙂

    You asked for suggestions to improve the coding: I think all of the data declarations can be replaced by inline declarations.

    Definitely I’ll play around with OData consumption using this method. Thank you for this idea.

    • Don’t try range_for_netamounts = VALUE #( ( sign = ‘I’ option = ‘BT’ low = ‘10000’   high= ‘20000’) ).

      It is not supported (yet).

        • I did not mean that you should not try an inline declaration for the ranges, though you are right that this doesn’t work.

          I meant that you should not try to use the option BT in your ranges, since it is not yet supported to use BT. But I updated my code so that it shows how can get around this restriction by simply creating another filter node and combine three of them.

  • I tried instead of accessing the Odata service from the public gateway to consume a API from the SAP API Business Hub. This was perfectly possible with the code snipped which is provided.

    The next way was to use your article to have a model so I can consume the data in a meaningful way not just the raw response.

    Instead of authorizing myself via a username and password I use the APIKey in the header field.

    In addition to that I added the Content-Type = ‘application/json’ and the Accept with the same value.

    Now my problem is that I get “The OData service has raised a Client Error ‘415’: Unsupported Media Type’. I tried to look a bit into this topic and some article state that it has something to do with the Content type not being set but I did set it.
    I furthermore tried to debug the coding and I am not sure but I think the setting of the Content-Type did not happen correctly.

    Do you have any idea where I got lost here?

    Thanks for your help

    CLASS zcl_order_confirmation IMPLEMENTATION.
    
    
    
      METHOD if_oo_adt_classrun~main.
    
        DATA: lt_business_data TYPE TABLE OF zorderconfirmation,
              lo_http_client   TYPE REF TO if_web_http_client,
              lo_client_proxy  TYPE REF TO /iwbep/if_cp_client_proxy,
              lo_request       TYPE REF TO /iwbep/if_cp_request_read_list,
              lo_response      TYPE REF TO /iwbep/if_cp_response_read_lst.
    
        TRY.
            "create HTTP client by destination
            lo_http_client = get_http_client( ).
    
            lo_client_proxy = cl_web_odata_client_factory=>create_v2_remote_proxy(
              EXPORTING
                iv_service_definition_name = 'ZSC_ORDERCONFIRMATION'
                io_http_client             = lo_http_client
                iv_relative_service_root   = '/sap/opu/odata/sap/API_PROD_ORDER_CONFIRMATION_2_SRV/' ).
    
            " Navigate to the resource and create a request for the read operation
            lo_request = lo_client_proxy->create_resource_for_entity_set( 'PRODNORDCONF2' )->create_request_for_read( ).
    
            lo_request->set_top( 1 )->set_skip( 0 ).
    
            " Execute the request and retrieve the business data
            lo_response = lo_request->execute( ).
            lo_response->get_business_data( IMPORTING et_business_data = lt_business_data ).
    
          CATCH /iwbep/cx_cp_remote INTO DATA(lx_remote).
            " Handle remote Exception
            " It contains details about the problems of your http(s) connection
    
          CATCH /iwbep/cx_gateway INTO DATA(lx_gateway).
            " Handle Exception
    
          CATCH cx_web_http_client_error cx_http_dest_provider_error.
            "handle exception
        ENDTRY.
      ENDMETHOD.
    
    
      METHOD get_http_client.
        DATA(http_destination) =  cl_http_destination_provider=>create_by_url(
            i_url = 'https://sandbox.api.sap.com/s4hanacloud/' ).
    
        "create HTTP client by destination
        r_http_client = cl_web_http_client_manager=>create_by_http_destination( http_destination ) .
    
        "adding headers with API Key for API Sandbox
        DATA(lo_web_http_request) = r_http_client->get_http_request( ).
        lo_web_http_request->set_header_fields( VALUE #(
        (  name = 'Content-Type' value = 'application/json' )
        (  name = 'Accept' value = 'application/json' )
        (  name = 'APIKey' value = '----' )
         ) ).
    
      ENDMETHOD.
    
    ENDCLASS.
    • Hi,

      Did you happen to find the answer to this?

      I have a similar issue. I tried to connect to an on prem odata service by means of an approuter deployed on my trial cloud foundry environment.

      I keep getting back the error “Server response has the wrong format (content-type) ‘text/html’.”

      My code is pretty much exactly the same as the one provided here in the blog.

      What I can’t figure out is how the response from a regular odata service comes back with content-type application/json, whereas the response from my approuter (where the only visible difference is a different base url) comes back with content type text/html.

      I anybody has any ideas?

      Best regards,

      Ann

  • Hi Andre Fischer, ,

    I am trying to expose the data from an on-premise system to Cloud. The first step in this series was achieved by using the class cl_http_destination_provider. Now when the data is available in the ABAP console, how can we use this data further so that the same is consumed on a Fiori Application on ABAP on Cloud Platform created using SAP Web IDE.

    I have also posted a question on ‘https://answers.sap.com/questions/13049978/how-to-use-the-data-exposed-using-cl-http-destinat.html’ , with appropriate explanation and screenshot.

    Looking forward to your expert suggestions.

    Regards,
    Udita