Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Andre_Fischer
Product and Topic Expert
Product and Topic Expert
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.

 



 

 

 

 
18 Comments
UweFetzer_se38
Active Contributor
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.
Andre_Fischer
Product and Topic Expert
Product and Topic Expert
0 Kudos
Don't try range_for_netamounts = VALUE #( ( sign = 'I' option = 'BT' low = '10000'   high= '20000') ).

It is not supported (yet).
UweFetzer_se38
Active Contributor
Okay, okay.

Corrected: "I think all of the data declarations (except the two ranges) can be replaced by inline declarations"
0 Kudos
We were checking on the alternative approach to call OData service from on-premise system..

Thank you for sharing 🙂
Andre_Fischer
Product and Topic Expert
Product and Topic Expert
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.
former_member638258
Discoverer
0 Kudos
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.
0 Kudos
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
annkoolen
Explorer
0 Kudos
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
spurkayastha
Explorer
0 Kudos
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
0 Kudos
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
sitakant_tripathy2
Active Participant
0 Kudos
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
former_member754280
Discoverer
0 Kudos
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.
former_member754587
Discoverer
0 Kudos
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
WRoeckelein
Active Participant
0 Kudos
Hi ann.koolen ,

an unexpected text/html is usually a name/password input form...

Regards,

Wolfgang
adamharkus
Participant
0 Kudos
Hi andre.fischer

Thanks for the article

I’ve implemented something similar to handle BT but how are multiple BTs (LE and GE) handled?

Also do you know if the BT fix is on SAPs roadmap and when it can be expected, as this would be a very helpful feature?

Best Wishes

Adam
abdullahgunes
Participant
0 Kudos
Hi All,

I am trying to work on RAP620-Github Repo on my local S4HANA system and some classes are not available, so I saw this blog and tried to solve my problem.

I am sharing my solution to help everyone.

 

I encountered the following errors during the development process, now it works as I expected.

Server response has the wrong format (content-type) 'text/html'.

Resource not found for the segment 'sap'.
CLASS zrap620_cl_ce_products_ag DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .

PUBLIC SECTION.

INTERFACES if_oo_adt_classrun .
TYPES t_product_range TYPE RANGE OF zrap620_agsepmra_i_product_e-Product.
TYPES t_business_data TYPE TABLE OF zrap620_agsepmra_i_product_e.

METHODS get_products
IMPORTING
it_filter_cond TYPE if_rap_query_filter=>tt_name_range_pairs OPTIONAL
top TYPE i OPTIONAL
skip TYPE i OPTIONAL
EXPORTING
et_business_data TYPE t_business_data
RAISING
/iwbep/cx_cp_remote
/iwbep/cx_gateway
cx_web_http_client_error
.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.



CLASS zrap620_cl_ce_products_ag IMPLEMENTATION.


METHOD if_oo_adt_classrun~main.
DATA business_data TYPE TABLE OF zrap620_agsepmra_i_product_e.
DATA filter_conditions TYPE if_rap_query_filter=>tt_name_range_pairs .
DATA ranges_table TYPE if_rap_query_filter=>tt_range_option .
ranges_table = VALUE #( ( sign = 'I' option = 'GE' low = 'HT-1200' ) ).
filter_conditions = VALUE #( ( name = 'PRODUCT' range = ranges_table ) ).

TRY.
get_products(
EXPORTING
it_filter_cond = filter_conditions
top = 3
skip = 1
IMPORTING
et_business_data = business_data
) .
out->write( business_data ).
CATCH cx_root INTO DATA(exception).
out->write( cl_message_helper=>get_latest_t100_exception( exception )->if_message~get_longtext( ) ).
ENDTRY.
ENDMETHOD.


METHOD get_products.
DATA: filter_factory TYPE REF TO /iwbep/if_cp_filter_factory,
filter_node TYPE REF TO /iwbep/if_cp_filter_node,
root_filter_node TYPE REF TO /iwbep/if_cp_filter_node.

DATA: http_client TYPE REF TO if_web_http_client,
lo_http_client TYPE REF TO if_http_client,
lo_client_proxy TYPE REF TO /iwbep/if_cp_client_proxy,
read_list_request TYPE REF TO /iwbep/if_cp_request_read_list,
read_list_response TYPE REF TO /iwbep/if_cp_response_read_lst.

cl_http_client=>create_by_url( EXPORTING url = 'https://sapes5.sapdevcenter.com'
IMPORTING client = lo_http_client ).
IF sy-subrc <> 0.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.

lo_http_client->request->set_header_fields( VALUE #( ( name = 'Content-Type' value = 'application/json' )
( name = 'Accept' value = 'application/json' ) ) ).

lo_client_proxy = /iwbep/cl_cp_client_proxy_fact=>create_v2_remote_proxy(
EXPORTING
is_proxy_model_key = VALUE #( repository_id = 'SRVD'
proxy_model_id = 'ZRAP620_SC_PRODUCTS_AG'
proxy_model_version = '0001' )
io_http_client = lo_http_client
iv_relative_service_root = '/sap/opu/odata/sap/ZPDCDS_SRV/' ).



" Navigate to the resource and create a request for the read operation
read_list_request = lo_client_proxy->create_resource_for_entity_set( 'SEPMRA_I_PRODUCT_E' )->create_request_for_read( ).
" Create the filter tree
filter_factory = read_list_request->create_filter_factory( ).
LOOP AT it_filter_cond INTO DATA(filter_condition).
filter_node = filter_factory->create_by_range( iv_property_path = filter_condition-name
it_range = filter_condition-range ).
IF root_filter_node IS INITIAL.
root_filter_node = filter_node.
ELSE.
root_filter_node = root_filter_node->and( filter_node ).
ENDIF.
ENDLOOP.

IF root_filter_node IS NOT INITIAL.
read_list_request->set_filter( root_filter_node ).
ENDIF.

IF top > 0 .
read_list_request->set_top( top ).
ENDIF.

read_list_request->set_skip( skip ).
" Execute the request and retrieve the business data
read_list_response = read_list_request->execute( ).
read_list_response->get_business_data( IMPORTING et_business_data = et_business_data ).
ENDMETHOD.
ENDCLASS.
Andre_Fischer
Product and Topic Expert
Product and Topic Expert
0 Kudos
It is hard to judge what went wrong for you.

In order to create an OData CLient Proxy in your SAP S/4HANA System one problem is to create an OData Client Proxy there in the first place.

Are you sure you have a working OData CLient Proxy in place?

Please check my updated blog post.

How to use the OData Client Proxy in SAP S/4 HANA or How to use the OData Service Consumption Model ...
NITIN_SAPRS
Explorer
0 Kudos

Hi @Andre_Fischer ,

Please help like how to add $Filter on Navigation Property in iv_property_path in Create_by_range ?

Thanks,

Nitin