SAP Tutorial: Serving Data from an On Premise System in a CAP Java Application (Part 4)

Part 4: Defining Services and Service Handlers

Brian Heise
9 min readApr 6, 2021
Photo by Cytonn Photography on Unsplash

Contents

Welcome to Part 3 of Serving Data from an On-Premise System in a CAP Java Application. So far we have learned how to define the connection to our on-premise system on BTP using a destination, how to set up a new CAP Java application with the necessary dependencies, and how to import an oData service from our on-premise system into our CAP application.

Now in Part 4 we will define an Application Service to serve our data, a Remote Service to handle data exchange with our on-premise system, and a custom service handler to direct the communication between these two services.

Step 1

First, we need to define our Application Service and our Remote Service. To do so, let’s open the application.yaml file in the folder “srv/src/main/resources/”. This file is used to define various metadata for our application, including our services.

By default CAP defines a default profile here under config.activate.on-profile. For developers familiar with Node, these profiles are ways to assign different settings for various environments. For now, you can consider the default profile under which we’ll write the following code to be your development environment. You’ll see that CAP has automatically provided the settings for a SQLite database.

Below the code already in the file, add the following lines:

cds:
application.services:
- name: "<your_service_name>"
model: "<cds_model_name>"

Let’s look at these lines one by one.

The cds on line 10 declares that the following indented lines will provide CAP-specific setting. You’ll notice spring on line 2 as well. That declared that the following settings are Spring-specific.

On line 11, we declare application.services. Below that we can explicitly declare Application Services — that is, the oData services that our application serves to it’s clients. By default, CAP defines such services for each oData service defined in CDS within the app, provided that it isn’t explicitly declared as a different type of service. If it is, then CAP will not automatically generate an Application Service for it.

On line 12 we provide a name for our application service. In this case, I decided to call it “proxy_service” because it’s intended to be a proxy for our remote service.

On line 13, we declare which CDS model will define the behavior of this service. I used “ZTEST_PROJECT_SRV” since that’s the name I used to define the model in CDS in Part 3 of this series.

Step 2

Now we’ll define our Remote Service. Add the following code to your application.yaml file under the Application Service that you just defined.

  remote.services:
- name: "<name_of_service>"
model: "<name_of_cds_model>"
destination:
name: "<destination_name>"
suffix: "sap/opu/odata/sap/"
type: "odata-v2"

Line 14 declares that the following lines will define our app’s remote services.

Line 15 declares the name of the service. This can be anything that you want, but if you use the same name as your CDS model, CAP will infer that that model should be associated with this service, so you won’t have to explicitly declare a model like we did before. However, if you want them to have different names, you will need to declare the model as we did with the application service.

Line 16 declares that the following lines will associate this service with a destination on BTP.

Line 17 declares the name of the service. This should be the name of the service you defined on BTP in Part 1 of this series.

Line 18 is the suffix that will be attached to the URL defined in the destination indicated on line 17. Since you’re connecting to an on premise system, you will need “sap/opu/odata/sap/” because this is the namespace that on-premise systems serve oData by default. Note that if your system does not use the default namespace you will need to adjust this line.

Line 19 declares what version of oData the service responds to. At the time of writing, on-premise systems still follow the oData V2 protocol, so you should set “odata-v2”.

For more information on remote services, see this document:
https://cap.cloud.sap/docs/java/remote-services

Step 3

Next we will begin writing our custom service handler. Open your main folder and find Application.java. It will be nested in folders under the application’s groupId and artifactId as you defined them when you generated the application in Part 2 of this series. In other words, the folder path is as follows: “srv/src/man/java/<groupId>/<artifactId>”

Within this folder, create a new folder called “handlers.” This is where we will store our custom handler.

Inside the folder, create a file called ProxyServiceHandler.java

Note: the name really doesn’t matter, but it’s considered best practice to give it the same name as the service it handles. In our case, our application services is called Proxy_Service.

If you look inside the file, you’ll find that the following code was automatically generated.

Step 4

First, we need to import our java-based service definition, which was automatically generated for us from the service we declared in CDS in Part 3 of this series when we ran mvn clean install. All such definitions are located in the folder “srv/src/gen/java/cds/gen”.

To import all the related files, add these lines to the top of the service handler you defined, just below the package definition.

import cds.gen.ztest_project_srv.*;

Step 5

Next we need a few more imports from the Spring Framework and from SAP’s CDS packages. We’ll talk more about what these are for later.

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.cds.CdsReadEventContext;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.Result;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.services.cds.CdsService;
import com.sap.cds.services.cds.RemoteService;

Step 6

Next we add the necessary annotations to identify the class defined in this file as the event handler for our oData service. Add the following code to our class declaration:

@Component
@ServiceName("proxy_service")
public class OnPremiseServiceHandler implements EventHandler {
}

Note that the argument for the @ServiceName annotation is the name of the application service that you defined in Step 1. This is annotation is part of the CAP framework and is necessary for all service handlers. It links the handler with the service that it handles.

If you’re new to Spring, the Java framework on which CAP Java is based, then you might not be familiar with the @Component annotation. Check this article to learn more about it.

The EventHandler that our service handler implements is also a standard part of CAP and is necessary to provide the basic capabilities common to all event handlers.

Step 7

Next we define our remote service inside our class definition. Add the following code:

@Autowired
@Qualifier(ZtestProjectSrv_.CDS_NAME)
RemoteService remote;

What we added here first was an @Autowired annotation, which is a standard part of the Spring framework. Check this article to learn more about it.

@Qualifier is also a Spring standard annotation. In this case, it takes the name of the Remote Service that we defined in Step 2. You’ll notice, however, that the argument is written as “ZtestProjectSrv_.CDS_NAME”. This value contains the name of our service as a string and was automatically generated by CAP. You can also write the name directly in as a string literal. To learn more about @Qualifier, check this article.

The final line we added here was the actual variable that holds our RemoteService instance. We don’t need to assign it to any value because the previous two annotations handled that for us.

Step 8

Next we will define an On event using the @On annotation, within which we will define the actual query that we will run.

Note: there are also Before and After events, which allow you to define preprocessing and post-processing logic. For more information on event handlers, see the following article:
https://cap.cloud.sap/docs/java/provisioning-api

@On(event = CdsService.EVENT_READ, entity = Purchaseorders_.CDS_NAME)
public void onRead(CdsReadEventContext context) {
}

The @On annotation takes three arguments: event, entity, and service (however, service is inferred from the @ServiceName annotation that we provided above, so we won’t have to enter it). The values for these can be entered as string literals, but here we are using the value stored in CdsService.EVENT_READ (provided by CAP) and Purchaseorders_.CDS_NAME (automatically generated by our CDS service definition). If you want to enter the name of your entity as a string literal, note that the value for entity follow this pattern: “<service_name>.<entity_name>”.

Notice that the input for this function is an instance of CdsReadEventContext. When the event is run, CAP will input this context as an argument. This will provide useful information about the event, for example the contents of the user’s request.

If we were to translate this code to English it means “When a read event for the Purchaseorders entity of the proxy_service service is occurs, run this function.”

Step 9

Next, let’s write the code that runs our query. Add the following code to our @On event:

    CqnSelect selectQuery = context.getCqn();
Result result = remote.run(selectQuery);
context.setResult(result);

The first line retrieves the contents of the user’s query from the event context. The second line sends the query to our remote service. The final line sets the result of the context, which is then passed back out to the framework, where it can be displayed.

Step 10

Next run mvn spring-boot:run to boot the application and open http://localhost:8080

You’ll see a page displaying the links to the entities of your Application Service. Try clicking one.

You should see the following error message:

This is because our destination is defined in the cloud, but not on our local machine. To connect to the on-premise system, we will have to deploy to Business Technology Platform. We will cover how to do that in Part 5 of this tutorial.

Conclusion

Here in Part 4 of Serving Data from an On Premise System in a CAP Java Application we learned how to define Application Services and Remote Services in our application.yaml. We also learned how to build a custom event handler to proxy requests to our on-premise system through an Application Service. In Part 5, we will cover how to deploy this application to BTP so that we can actually retrieve data from our on-premise system.

Contents

Support

Did you like this blog? Want to make sure I can keep creating them? Then consider subscribing on Patreon!

--

--

Brian Heise
Brian Heise

Written by Brian Heise

Full Stack web developer employed at Liferay Japan

No responses yet