In the previous tutorial, we created our first working SAP OData Service. It was OData at it most simplest form with just one method re-defined to get POHeaderSet. We will work on the same ZGW_PO_SRV service of the earlier post and add more complexity and features to dive deep into OData implementation and put forth the concept behind it.
All the tutorial series in SAPSPOT are designed to commence from simple terms with simple examples and build on the more practical complex scenario later. If you are a beginner on OData and if you have not visited our Introduction to OData and Part II (Create First OData Service) then we highly recommend you to pause here for a while, have a quick look into those two topics before you move forward with this post. If you already understand the terms and concepts or if you have fairly good exposure of OData, please continue and enjoy.
A. READ Operation (i.e. GET_ENTITY method)
“GET” is the HTTP Method for all external Consumer Applications which call the URI of the OData Service.
Till now we learned that our URI ‘/sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet’ works in GW Client and the corresponding Browser URI works in the browser. We have re-defined just one method POHEADERSET_GET_ENTITYSET. It returns an array or internal table of PO Header data. But, suppose the front end application of the consumer system wants to connect to SAP using our OData service by providing the PO number and pull only that PO numbers details. In short, if the consumer application expects only one row of information. How can they achieve it?
The URI for the consumer system should look like below. They need to pass the primary Key as the parameter of the URI.
URI to READ: /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002012’).
This means we want to see only PO number ‘4500002012’ details. Let us check what output we get with our current OData service which we created in our last post.
We get the error message ‘Method ‘POHEADERSET_GET_ENTITY’ not implemented in data provider class’.
What does the error message mean? It implies, if we want just one entry data, the Data Provider Class (DPC) would call the POHEADERSET_GET_ENTITY method and not the POHEADERSET_GET_ENTITYSET method. We need to correlate, work area with the entity and array/internal table with entity set.
So, as the error message suggests, let us implement the ‘POHEADERSET_GET_ENTITY’ method in t-code SEGW. Right click on the GetEntity (Read) Operation. Choose Go to ABAP Workbench. Information message would pop up. Hit continue.
Select the method ‘POHEADERSET_GET_ENTITY’ and hit the Redefine method icon as explained in the earlier post. Alternatively, we can right click on the method and choose ‘Redefine’ option. For a change, I have taken the second route this time.
Remove the auto-generated code and use code similar to the snippet shown below in method POHEADERSET_GET_ENTITY.
DATA : ls_key_tab LIKE LINE OF it_key_tab,
lv_ebeln TYPE ekko-ebeln.
* IT_KEY_TAB has key name and value
READ TABLE it_key_tab INTO ls_key_tab
WITH KEY name = 'Ebeln'. " Case sensitive
IF sy-subrc EQ 0.
lv_ebeln = ls_key_tab-value.
ENDIF.
* Select one PO entry
SELECT SINGLE * FROM ekko INTO CORRESPONDING FIELDS OF er_entity
WHERE ebeln = lv_ebeln.
For the URI ‘/sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002012′)’, the importing paramter IT_KEY_TAB would hold ‘Ebeln’ and ‘4500002012’ as ‘name’ and ‘value‘ respectively.
Let us see the output again. Got to t-code /n/IWFND/GW_CLIENT or using the Browser URI and execute. This time it returns the details of the PO passed on the URI parameter.
What did we learn just now? We need to pass the KEY field value in the URI in order to fetch its details.
Let us change the format to JSON and see how it looks. The URI is /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002012’)?$format=json
The same output in JSON format looks like below in the browser.
The JSON format looks cleaner. Some developers might toggle around with the XML and JSON format to view the information passed by OData and then design the solution.
How to pass mulitple fields of the Composite Primary Key?
/sap/opu/odata/sap/ZWM_ODATA_SRV/ZZCALIBRATE_JSON_Set(CMSASSET=’CMS456788879′,LAST_CAL_DATE=’20160923′,CAL_DUE_DATE=’20230923′)
When you pass the complete primary keys, your importing key table in the Get Methods would be populated, i.e. IT_KEY_TAB will have primary key field name and value.
In the above example dates are defined as characters therefore it is straight character format. But if you define it as dats, you need to pass the date field accordingly.
If you pass just the filter (not complete primary keys), then IT_KEY_TAB will not be populated. You need to check for IV_FILTER_STRING or IV_SEARCH_STRING.
/sap/opu/odata/sap/ZWM_ODATA_SRV/ZZCALIBRATE_JSON_Set?&$filter=CMSASSET eq ‘CMS456788879’
In this filter case, look for IV_FILTER_STRING in the GET_ENTITYSET method.
Let us step back. Now let us execute the URI with just POHeaderSet i.e. /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet. Let us see what info comes up in the result.
i. You get the link for the browser
ii. You get the link to pull one particular entity type information, i.e. just one PO detail
As we keep enhancing the OData Model and Service, the information in the result set keeps growing with more links and navigation options. We will witness them gradually as we reach that stage of our tutorial.
B. READ one particular field or property of the Entity Type (/Field1)
At times, we need to pull just one field of the structure for the key element. For example, you know the PO number from somewhere in your application, now you need to find our the Vendor who is supplied the material.
So the URI is /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002013’)/Lifnr.
Simple, just add the property name i.e the field name of the Entity Type at the URI.
Similarly, if you want to know the company code for which the PO was created, you know how to pull it.
URI would be /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002013’)/Bukrs.
C. Count the number of entries in the system ($count)
In many cases, we need to know how many entries exist for our Query. You might need to know the count just to report or to plan some action based on it. Whatever may be the reason, you need to add $count at the end of your URI to find the number count.
URI: /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet/$count
We have hard-coded UP TO 10 Rows in our selection statement, therefore the Query Count shows as 10. Remove the UP TO 10 from code and check the count in your system.
A tough question for you now. What would be the count of the READ URI?
URI: /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002012’)/$count
It has to be one, right? After all, we are passing the Primary Key as the URI parameter.
D. Extract only some fields of the Entity Type ($select=Field1,Field2,Field3)
You have an OData Service with data model having 10 properties(fields), but in your consuming application, you need just three fields. You can pull all the fields and then filter in your application. But smart developers would only pull what is needed. Nothing more nothing less.
Say for our POHeaderSet entity, we need Company Code, Created By and the PO number. Our URI would be /sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet?$select=Ernam,Bukrs,Ebeln.
If you want to view the same output in JSON format, append &$format=json at the end.
/sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet?$select=Ernam,Bukrs,Ebeln&$format=json
FYI: Whenever you find a question mark “?”, you should understand, it is the beginning of a query option.
The above are some standard query options which do not need any backend programming in OData model/service. There are some more query options which need coding. We will discuss them some other day in a separate post.
E. Set Breakpoint and check the Methods called in Debug mode
In Part II of our tutorial, I informed you that we are in “Hub Deployment with Development in the Backend System”. I created the OData Model and Service in our backend system and I am testing the service in our front end system. Let us now debug and investigate which method gets triggered while READ (one entry, GetEntity) and QUERY (internal table, GetEnititySet) operations are executed.
In the backend system, go to t-code SEGW. Expand the Service Implementation Folder and POHeaderSet and right click on GetEntity (Read) Operation, choose Go to ABAP Workbench. It will take you to the related backend method of the DPC (Data Provider Class). If you know the extension class of your DPC, you can directly go to it using SE24 or SE80 as you would have accessed any other regular class method.
Put an External Breakpoint (not session breakpoint) on methods POHEADERSET_GET_ENTITY and POHEADERSET_GET_ENTITYSET as we would be calling the service from the browser or from our front end system.
Let us go to the t-code /n/IWFND/GW_CLIENT in the front end system and execute ‘/sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet’ with HTTP Method GET. Hit the execute button.
We have breakpoints at two methods, but only the POHEADERSET_GET_ENTITY_SET method is triggered as our URI is specified for the whole set (i.e. it is a Query Operation). SAP Netweaver Gateway has inbuilt capability to figure out the right method of the DPC. Isn’t it cool? Developers just need to write the correct logic to fetch the data. Integration between service and model is done by Netweaver Gateway.
Now, let us execute the URI ‘/sap/opu/odata/sap/ZGW_PO_SRV/POHeaderSet(‘4500002013′)’ to Read just one entry of the DataSet. Execute and your debugger would stop at the breakpoint in method POHEADERSET_GET_ENTITY.
This was too obvious, but still, we wanted to show it here as some of you might still do not have the right SAP NetWeaver system. Our screenshots would help those enthusiastic readers.
Enough of POHeaderSet. Let us get some Item data as well. After all, everything is not alway on the head.
F. Redefine the POItem methods for READ and QUERY Operations
Go to SEGW t-code again, ZGW_PO->Service Implementation->POItemSet->GetEntity (Read) -> Right Click -> Go to ABAP Workbench.
You will get an information saying it has not been implemented. Carry on, it will take you to the Class Builder screen. Redefine the method ‘POITEMSET_GET_ENTITY’.
Remove the auto-generated commented code and write your logic to fetch the data. You can refer the below code. Importing parameter IT_KEY_TAB has the ‘field name’ and the ‘value’ of the Key fields which are expected to be passed to the Method (usually as the Parameters of the URI).
METHOD poitemset_get_entity.
DATA: ls_key_tab TYPE /iwbep/s_mgw_name_value_pair,
lv_ebeln TYPE ebeln,
lv_ebelp TYPE ebelp.
* Get the key property values
READ TABLE it_key_tab INTO ls_key_tab WITH KEY name = 'Ebeln' .
IF sy-subrc = 0.
lv_ebeln = ls_key_tab-value.
ENDIF.
* Get the key property values
READ TABLE it_key_tab INTO ls_key_tab WITH KEY name = 'Ebelp' .
IF sy-subrc = 0.
lv_ebelp = ls_key_tab-value.
ENDIF.
* Select one row
SELECT SINGLE * FROM ekpo INTO CORRESPONDING FIELDS OF er_entity
WHERE ebeln = lv_ebeln
AND ebelp = lv_ebelp.
ENDMETHOD.
Similarly, redefine the method ‘POITEMSET_GET_ENTITYSET’ to write your logic to pull the array of data, i.e. internal table. You can refer to the snippet below.
METHOD poitemset_get_entityset.
DATA: ls_key_tab TYPE /iwbep/s_mgw_name_value_pair,
lv_ebeln TYPE ebeln.
* Get the key property values
READ TABLE it_key_tab WITH KEY name = 'Ebeln' INTO ls_key_tab.
IF sy-subrc = 0.
lv_ebeln = ls_key_tab-value.
ENDIF.
IF lv_ebeln IS NOT INITIAL.
SELECT * FROM ekpo INTO CORRESPONDING FIELDS OF TABLE et_entityset
WHERE ebeln = lv_ebeln.
ELSE.
SELECT * UP TO 10 ROWS FROM ekpo INTO CORRESPONDING FIELDS OF TABLE et_entityset.
ENDIF.
ENDMETHOD.
Please note, all the code snippets are only for reference. The actual logic would depend on the project requirement and the application’s need.
Now we can perform all the Query Operations on POItemSet which we did for the POHeaderSet. Please pardon the brevity of the below points as we have already provided the details above for HeaderSet.
i. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet
Please take note of all the href (links). They work as hints for the developers to build their queries in the consumer applications.
ii. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet?$format=json
iii. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet(Ebeln=’6000000251′,Ebelp=’00010′)
Here we have used the href we found on the above URI result.
iv. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet?$select=Ebeln,Ebelp,Werks
Pull only three fields from the Data Model.
v. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet?$select=Ebeln,Ebelp,Werks&$format=json
By now you should know if we need to apply two sets of query options, we can do it by using Ampersand “&”. For this example we are using “select&format” query option.
vi. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet/$count
vi. /sap/opu/odata/sap/ZGW_PO_SRV/POItemSet(Ebeln=’6000000251′,Ebelp=’00010′)/Matnr
Fetch just one field from the URI.
Let us stop here for today. In this tutorial, we learned how to redefine the methods and write our custom logic. We debugged and confirmed that GET_ENTITY method is called for READ Operation and GET_ENTITYSET method is called for QUERY (array) Operation. We also acquainted ourselves with the commonly used Query Options.
Till now our data is fetched using GET HTTP method call and POHeader and POIteam are retrieved independently. In subsequent articles, we would show POST, PUT, PATCH, DELETE etc HTTP Methods. Also, in real projects, we need to pull item data based on Header and vice-versa (Entity Types needs to be inter-connected). So there should be some link for the data to flow between datasets. We can achieve it by Associations and Navigations, which we will definitely see in our next post.
Play around with the Query Options. Make your own parameters and learn what you can do and what you cannot. All these URIs would be needed in our SAPUI5 application which we will create in the due course of our tutorial to consume this OData Service and its URIs from our SAPUI5 application.
Bonus Contents for this Post
i. How does the data in importing parameter IT_KEY_TAB look like?
ii. What is the difference between URN, URL and URI?
I do not have much knowledge about these terms and how it works. And as ABAPers, we might not need to know the details as well. But we should have some understanding of these abbreviations.
These are some high-level components of a URI. Protocol, Host, Port, Service, Resource, Query Options.
In layman’s term the:
URN = Mickey Mouse (Name) – a Name.
URL = Disney World, Florida (Location) – a Locator. How can we reach the Location? Flight, Cruise, by Road or Train? You can walk too. The mechanism to reach to the location can be compared to the protocols like HTTP(S), FTP, MAILTO etc. A URL needs to specify a Protocol (it is a must have criteria)
URI = an Identifier.
Both Name and Locator can be an Identifier. Hence URN and URL can be URI.
As mentioned above, URL also needs to tell how to reach the Locator. Can we reach it by http protocol or ftp or mailto etc. Whenever the protocol is specified, it becomes a URL.
So all URIs are not URLs but all URLs are URI.
In SAP t-code /IWFND/GW_CLIENT, when we are testing our OData Service, we are using the URI or URL? To be on safer side we should say in one voice URI. But check the screen one more time. We are also specifying the Protocol as HTTP. So, if you say, we are using a URL, you are technically correct too.
Wiki summarizes it very precisely:
“One can classify Uniform Resource Identifier (URIs) as locators (URLs), or as names (URNs), or as both. A Uniform Resource Name (URN) functions like a person’s name, while a Uniform Resource Locator (URL) resembles that person’s street address. In other words: the URN defines an item’s identity, while the URL provides a method for finding it”.