Skip to Content
Technical Articles
Author's profile photo Andre Fischer

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.

 

 

 

 

 

Assigned tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Uwe Fetzer
      Uwe Fetzer

      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.

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

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

      It is not supported (yet).

      Author's profile photo Uwe Fetzer
      Uwe Fetzer

      Okay, okay.

      Corrected: "I think all of the data declarations (except the two ranges) can be replaced by inline declarations"

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      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.

      Author's profile photo ANURAG SINGH
      ANURAG SINGH

      We were checking on the alternative approach to call OData service from on-premise system..

      Thank you for sharing 🙂

      Author's profile photo Andreas Wenzl
      Andreas Wenzl

      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.
      Author's profile photo Ann Koolen
      Ann Koolen

      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

      Author's profile photo Shillpa `Gupta
      Shillpa `Gupta

      Hello Andre,

      This blog is really informative, Thanks for writing this.

      Further on this topic I want to proceed and build a fiori application using the same service which I have imported through metadata file. I have posted my question on the below tag:

      https://answers.sap.com/questions/13008490/error-while-creating-the-service-binding-in-abap-c.html

      Could you please let me know the further usage.

       

      Thanks & Regards

      Shilpa Gupta

      Author's profile photo Sangita Purkayastha
      Sangita Purkayastha

      Hi Andre Fischer,

      I have a query on how to expose CDS views and its corresponding Service Definitions and Service Bindings created in an on-premise system to the ABAP instance on the cloud.

      I have posted the query here:

      https://answers.sap.com/questions/13052525/exposing-cds-corresponding-service-definition-serv.html

      Can you please guide?

      Regards,

      Sangita Purkayastha

      Author's profile photo Udita Saklani
      Udita Saklani

      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

      Author's profile photo Sitakant Tripathy
      Sitakant Tripathy

      Hi Andre Fischer,

      hope you keeping well. Just seeking your thoughts on the OData consumption model generating a BDEF and Service for the Odata entity. In what capacity could these be used?

       

      Regards,

      Sitakant

      Author's profile photo Kani A
      Kani A

      Hi Andre,

      Thanks for the detailed blog.

      We would like to know " how  to navigate / associate between two or more entities " after odata service consumption in SAP BTP ABAP .  We have ongoing development and would be really helpful if you find some time to answer this.

       

      Regards,

      kani.

      Author's profile photo Sarath Kumar Jonnalagadda
      Sarath Kumar Jonnalagadda

      Hi Andre,

      In continuation of Kani question above, when we are calling the CRM_BUPA_ODATA from ABAP on Cloud for navigation property "WORKADDRESS", it's giving below error.

      "Navigation properties are currently not supported for select ('_WORKADDRESS')"

      Can you please let us know, if the navigation properties are not allowed?

       

      Regards,

      Sarath J