Search

Migration

Towards the new CAP Java SDK

To make the CAP Java SDK and therefore the applications built on it future-proof, we revamped the CAP Java SDK. Compared the classic CAP Java Runtime (also known as the “Java Gateway stack”), the new CAP Java SDK has numerous benefits:

  • Starts up much faster
  • Supports local development with SQLite
  • Has clean APIs to register event handlers
  • Integrates nicely with Spring and Spring Boot
  • Supports custom protocol adapters (OData V4 support included)
  • Has a modular design: Add features as your application grows
  • Enables connecting to advanced SAP Cloud Platform services like Enterprise Messaging

We strongly recommend adopting the new CAP Java SDK when starting a new project. Existing projects that currently use the classic CAP Java Runtime should consider adopting the new CAP Java SDK midterm to make use of new features and the superior architecture. In the following sections, we describe the steps to migrate a Java project from the classic CAP Java Runtime to the new CAP Java SDK.

Migrate the Project Structure

Create a new CAP Java project beside your existing one, which you want to migrate. You can use the CAP Java Maven archetype to create a new CAP Java project:

mvn archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds -DarchetypeVersion=RELEASE

Further details about creating a new CAP Java project and the project structure itself can be found in section Starting a New Project.

By default, the Java service module goes to the folder srv. If you want to use a different service module folder, you have to adapt it manually. Rename the service module folder to your preferred name and adjust also the <modules> section in the file pom.xml in your projects root folder:

...
<modules>
	<module>srv</module> <!-- replace srv with your folder name -->
</modules>
...

If you’ve changed the service module folder name, you have to consider this in the next steps.

Copy the CDS Model

Now, you can start migrating your CDS model from the classic project to the newly created CAP Java project.

Therefore, copy your CDS model and data files (*.cds & *.csv) manually from the classic project to the corresponding locations in the new project, presumably the db folder. If you organize your CDS files within subfolders, also re-create these subfolders in the new project to ensure the same relative path between copied CDS files. Otherwise, compiling your CDS model in the new project would fail.

Usually the CDS files are located in the following folders:

Usage Location in classic project Location in new CAP Java project
Database Model <CLASSIC-PROJECT-ROOT>/db/** <NEW-PROJECT-ROOT>/db/**
Service Model <CLASSIC-PROJECT-ROOT>/srv/** <NEW-PROJECT-ROOT>/srv/**

If your CDS model depends on other reusable CDS models, add those dependencies to <NEW-PROJECT-ROOT>/package.json:

...
"dependencies": {
	"@sap/cds": "^3.0.0",
	...  // add your CDS model reuse dependencies here
},
...

Note: In your CDS model, ensure that you explicitly define the data type of the elements whenever an aggregate function (max, min, avg etc.) is used, else the build might fail. In the following example, element createdAt has an explicitly specified datatype (that is timestamp).

view AddressView as select from Employee.Address {
    street, apartment, postal_code, MAX(createdAt) AS createdAt: timestamp
};

CDS Configuration

The CDS configuration is also part of <PROJECT-ROOT>/package.json and has to be migrated as well from the classic to the new project. Therefore, copy and replace the whole cds section from your classic package.json to the new project:

...
"dependencies": {
	"@sap/cds": "^3.0.0",
},
"cds": { // copy this CDS configuration from your classic project
	...
}
...

If there’s also a <CLASSIC-PROJECT-ROOT>/.cdsrc.json in your classic project to configure the CDS build, copy this file to the new project.

You can validate the final CDS configuration by executing a CDS command in the root folder of the new project:

cds env

It prints the effective CDS configuration on the console. Check, that this configuration is valid for your project. Execute this command also in your classic project and compare the results, they should be same.

Further details about effective CDS configuration can be found in section Effective Configuration.

First Build and Deployment

After you’ve copied all your CDS files, maintained additional dependencies and configured the CDS build, you can try to build your new CAP Java project the first time. Therefore, execute the following Maven command in the root folder of your new CAP Java project:

mvn clean install

If this Maven build finishes successfully, you can optionally try to deploy your CDS model to an SAP HANA database by executing the following CDS command:

cds deploy --to hana

Further details about deploying to SAP HANA can be found in section SAP HANA Cloud.

Migrate Java Business Logic

Migrate Dependencies

Now, it’s time to migrate your Java business logic. If your event handlers require additional libraries that go beyond the already provided Java Runtime API, add those dependencies manually to section dependencies in file <NEW-PROJECT-ROOT>/srv/pom.xml, for example:

...
<dependencies>
	<!-- add your additional dependencies here -->
	...
	<dependency>
		<groupId>com.sap.cds</groupId>
		<artifactId>cds-starter-spring-boot-odata</artifactId>
	</dependency>
	<dependency>
		<groupId>org.xerial</groupId>
		<artifactId>sqlite-jdbc</artifactId>
	</dependency>
	...
</dependencies>
...

Don’t add any dependencies of the classic Java Runtime to the new project. Those dependencies are already replaced with the corresponding version of the new CAP Java SDK.

Migrate Event Handlers

In the next steps, you have to adapt your Java classes to be compatible with the new Java Runtime API. That means, you’ll copy and migrate your event handler classes from the classic to the new project. It will be required to modify and adapt your Java source code to be compatible with the new Java SDK.

Usually the event handler classes and tests are located in these folders:

Usage Location in classic project Location in new CAP Java project
Handler classes <CLASSIC-PROJECT-ROOT>/srv/src/main/java/** <NEW-PROJECT-ROOT>/srv/src/main/java/**
Test classes <CLASSIC-PROJECT-ROOT>/srv/src/test/java/** <NEW-PROJECT-ROOT>/srv/src/test/java/**

Copy your Java class files (*.java) manually from the classic project to corresponding locations in the new project. It’s important that you re-create the same subfolder structure in the new project as it is in the classic project. The subfolder structure reflects the Java package names of your Java classes.

Annotations

Annotate all of your event handler classes with the following annotations and ensure a unique service name:

@org.springframework.stereotype.Component
@com.sap.cds.services.handler.annotations.ServiceName("serviceName")

Note, that all event handler classes also have to implement the marker interface com.sap.cds.services.handler.EventHandler. Otherwise, the event handlers defined in the class won’t get called.

Finally, your event handler class has to look similar to this example:

import org.springframework.stereotype.Component;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.ServiceName;

@Component
@ServiceName("AdminService")
public class AdminServiceHandler implements EventHandler {

}

The new CAP Java runtime introduces new annotations for event handlers. Replace event annotations at event handler methods according to this table:

Classic Java Runtime CAP Java SDK
@com.sap.cloud.sdk.service.prov.api.annotations.BeforeCreate(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.Before(event = com.sap.cds.services.cds.CdsService.EVENT_CREATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.BeforeDelete(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.Before(event = com.sap.cds.services.cds.CdsService.EVENT_DELETE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.BeforeRead(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.Before(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.BeforeQuery(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.Before(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.BeforeUpdate(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.Before(event = com.sap.cds.services.cds.CdsService.EVENT_UPDATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.operations.Create(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.On(event = com.sap.cds.services.cds.CdsService.EVENT_CREATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.operations.Delete(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.On(event = com.sap.cds.services.cds.CdsService.EVENT_DELETE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.operations.Query(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.On(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.operations.Read(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.On(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.operations.Update(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.On(event = com.sap.cds.services.cds.CdsService.EVENT_UPDATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.AfterCreate(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.After(event = com.sap.cds.services.cds.CdsService.EVENT_CREATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.AfterRead(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.After(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.AfterQuery(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.After(event = com.sap.cds.services.cds.CdsService.EVENT_READ, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.AfterUpdate(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.After(event = com.sap.cds.services.cds.CdsService.EVENT_UPDATE, entity = "yourEntityName")
@com.sap.cloud.sdk.service.prov.api.annotations.AfterDelete(entity = "yourEntityName") @com.sap.cds.services.handler.annotations.After(event = com.sap.cds.services.cds.CdsService.EVENT_DELETE, entity = "yourEntityName")

Event Handler Signatures

The basic signature of an event handler method is void process(EventContext context). However, it doesn’t provide the highest level of comfort. Event handler signatures can vary on three levels:

  • EventContext arguments
  • POJO-based arguments
  • Return type

Argument types of classic Java Runtime aren’t supported anymore and can be replaced according to this table:

Classic Java Runtime New CAP Java SDK
com.sap.cloud.sdk.service.prov.api.request.CreateRequest com.sap.cds.services.cds.CdsCreateEventContext
com.sap.cloud.sdk.service.prov.api.request.DeleteRequest com.sap.cds.services.cds.CdsDeleteEventContext
com.sap.cloud.sdk.service.prov.api.request.QueryRequest com.sap.cds.services.cds.CdsReadEventContext
com.sap.cloud.sdk.service.prov.api.request.ReadRequest com.sap.cds.services.cds.CdsReadEventContext
com.sap.cloud.sdk.service.prov.api.request.UpdateRequest com.sap.cds.services.cds.CdsUpdateEventContext
com.sap.cloud.sdk.service.prov.api.ExtensionHelper no direct replacement available

You can also get your entities injected by adding an additional argument with one of the following types:

  • java.util.stream.Stream<yourEntityType>
  • java.util.List<yourEntityType>

For more details, see section Implementing Event Handlers

Also replace the classic handler return types with the corresponding new implementation:

Classic Java Runtime New CAP Java SDK
return com.sap.cloud.sdk.service.prov.api.exits.BeforeCreateResponse call com.sap.cds.services.cds.CdsCreateEventContext::setResult(..) or return com.sap.cds.Result
return com.sap.cloud.sdk.service.prov.api.exits.BeforeDeleteResponse call com.sap.cds.services.cds.CdsDeleteEventContext::setResult(..) or return com.sap.cds.Result
return com.sap.cloud.sdk.service.prov.api.exits.BeforeQueryResponse call com.sap.cds.services.cds.CdsReadEventContext::setResult(..) or return com.sap.cds.Result
return com.sap.cloud.sdk.service.prov.api.exits.BeforeReadResponse call com.sap.cds.services.cds.CdsReadEventContext::setResult(..) or return com.sap.cds.Result
return com.sap.cloud.sdk.service.prov.api.exits.BeforeUpdateResponse call com.sap.cds.services.cds.CdsUpdateEventContext::setResult(..) or return com.sap.cds.Result

Delete Obsolete Files

There are numerous files in your classic project, which aren’t required and supported anymore in the new project. Don’t copy any of the following files to the new project:

<PROJECT-ROOT>/
|-- db/
|   |-- .build.js
|   `-- package.json
`-- srv/src/main/
            |-- resources/
            |    |-- application.properties
            |    `-- connection.properties
            `-- webapp/
                 |-- META-INF/
                 |   |-- sap_java_buildpack/config/resources_configuration.xml
                 |   `-- context.xml
                 `-- WEB-INF/
                     |-- resources.xml
                     |-- spring-security.xml
                     `-- web.xml

Security Settings

For applications based on Spring Boot, the new CAP Java SDK simplifies configuring authentication significantly: Using the classic CAP Java Runtime, you had to configure authentication for all application endpoints (including the endpoints exposed by your CDS model) explicitly. The new CAP Java SDK configures authentication for all exposed endpoints automatically, based on the security declarations in your CDS model.

Authorization can be accomplished in both runtimes with CDS model annotations @requires and @restrict as described in section Authorization and Access Control. Making use of the declarative approach in the CDS model is highly recommended.

In addition, the new CAP Java SDK enables using additional authentication methods. For instance, you can use basic authentication for mock users, which are useful for local development and testing (see section Mock Users for more details).

An overview about the general security configuration in the new CAP Java SDK can be found in section Security.

Configuration and Dependencies

To make use of authentication and authorization with JWT tokens issued by XSUAA on the SAP Cloud Platform, add the following dependency to your pom.xml:

<dependency>
	<groupId>com.sap.cds</groupId>
	<artifactId>cds-feature-xsuaa</artifactId>
</dependency>

This feature provides utilities to access information in JWT tokens, but doesn’t activate authentication by default. Therefore, as in the classic CAP Java Runtime, activate authentication by adding a variant of the XSUAA library suitable for your application (depending on if you use Spring, Spring Boot, plain Java) as described in the following sections:

Spring Boot

Activate Spring security with XSUAA authentication by adding the following Maven dependency:

<dependency>
	<groupId>com.sap.cloud.security.xsuaa</groupId>
	<artifactId>xsuaa-spring-boot-starter</artifactId>
	<version>${xsuaa.version}</version>
</dependency>

Usually, maintaining a spring-security.xml file or a custom WebSecurityConfigurerAdapter isn’t necessary anymore because the new CAP Java SDK runtime autoconfigures authentication in the Spring context according to your CDS model:

  • Endpoints exposed by the CDS model annotated with @restrict are automatically authenticated.
  • Endpoints exposed by the CDS model not annotated with @restrict are public by definition and hence not authenticated.
  • All other endpoints the application exposes manually through Spring are authenticated. If you need to change this default behavior either manually configure these endpoints or turn off auto configuration of custom endpoints by means of the following application configuration parameter:

    cds.security.authenticate-unknown-endpoints: false
    

Plain Java

The existing authentication configuration stays unchanged. No autoconfiguration is provided.

Enforcement API & Custom Handlers

The new CAP Java SDK offers a technical service called AuthorizationService, which serves as a replacement for the former Enforcement APIs. Obtain a reference to this service just like for all other services, either explicitly through a ServiceCatalog lookup or per dependency injection in Spring:

@Autowire
AuthorizationService authService;

Information of the request user is passed in the current RequestContext:

EventContext context;
UserInfo user = context.getUserInfo();

or through dependency injection within a handler bean:

@Autowire
UserInfo user;

With the help of these interfaces, the classic enforcement API can be mapped to the new API as listed in the following table:

classic API new API Remarks
isAuthenticatedUser(String serviceName) authService.hasServiceAccess(serviceName, event)  
isRegisteredUser(String serviceName) no substitution required  
hasEntityAccess(String entityName, String event) authService.hasEntityAccess(entityName, event)  
getWhereCondition() authService.calcWhereCondition(entityName, event)  
getUserName() user.getName() The user’s name is also referenced with $user and used for managed aspect.
getUserId() user.getId()  
hasUserRole(String roleName) user.hasRole(roleName)  
getUserAttribute(String attributeName) user.getAttribute(attributeName)  
isContainerSecurityEnabled() no substitution required  

Data Access and Manipulation

There are several ways of accessing data. The first and most secure way is to use CdsService instance. The second is to use PersistenceService, in that case the query execution is done directly against underlying datasource, bypassing all authority checks available on service layer. The third one is to use CDS4J component called CdsDataStore, which also executes queries directly.

Access CDS Service in Custom Handler and Query Execution

To access a CDS Service in custom handler and to execute queries, perform the following steps:

1) Inject the instance of CdsService in your custom handler class:

	@Resource(name = "CatalogService")
	private CdsService cdsService;

For more details, see section CDS Services

2) In each custom handler, replace instance of DataSourceHandler as well as CDSDataSourceHandler with the CdsService instance.

3) Rewrite and execute the query (if any).

Example of query execution in Classic Java Runtime:

CDSDataSourceHandler cdsHandler = DataSourceHandlerFactory.getInstance().getCDSHandler(getConnection(), queryRequest.getEntityMetadata().getNamespace());

CDSQuery cdsQuery = new CDSSelectQueryBuilder("CatalogService.Books")
	.selectColumns("id", "title")
	.where(new ConditionBuilder().columnName("title").IN("Spring", Java"))
	.orderBy("title", true)
	.build();
	
cdsHandler.executeQuery(cdsQuery);

For more details, see section CDS Data Source

The corresponding query and its execution in New CAP Java SDK looks as follows:

Select query =  Select.from("CatalogService.Books")
	.columns("id", "title")
	.where(p -> p.get("title")
	.in("Spring", "Java"))
	.orderBy("title");
	
cdsService.run(query);

For more details, see section Query Builder API

4) Rewrite and execute the CRUD operations (if any).

Action Classic Java Runtime New CAP Java SDK
Create dsHandler.executeInsert(request.getData(), true) cdsService.run(event.getCqn()) or cdsService.run(Insert.into("Books").entry(book))
Read dsHandler.executeRead(request.getEntityMetadata().getName(), request.getKeys(), request.getEntityMetadata().getElementNames()); cdsService.run(event.getCqn()) or cdsService.run(Select.from("Books").where(b->b.get("ID").eq(42)))
Update dsHandler.executeUpdate(request.getData(), request.getKeys(), true) cdsService.run(event.getCqn()) or cdsService.run(Update.entity("Books").data(book))
Delete dsHandler.executeDelete(request.getEntityMetadata().getName(), request.getKeys()) cdsService.run(event.getCqn()) or cdsService.run(Delete.from("Books").where(b -> b.get("ID").eq(42)))

As you can see in New CAP Java SDK it’s possible to either directly execute a CQN of the event, or you can construct and execute your own custom query.

For more deatails, see section Query Builder API

Accessing PersistenceService

If for any reason you decided to use PersistenceService instead of CdsService in your custom handler, you need to inject the instance of PersistenceService in your custom handler class.

@Autowired
private PersistenceService persistence;

For more details, see section Persistence API

Example of Query execution in Classic Java Runtime:

CDSDataSourceHandler cdsHandler = ...;

CDSQuery cdsQuery = new CDSSelectQueryBuilder("CatalogService.Books")
	.selectColumns("id", "title")
	.where(new ConditionBuilder().columnName("title").IN("Spring", Java"))
	.orderBy("title", true)
	.build();
	
cdsHandler.executeQuery(cdsQuery);

The corresponding query execution in New CAP Java SDK looks as follows:

Select query =  Select.from("CatalogService.Books")
	.columns("id", "title")
	.where(p -> p.get("title")
	.in("Spring", "Java"))
	.orderBy("title");
	
persistence.run(query);

Accessing CdsDataStore

If you want to use CdsDataStore in your custom handler, you first need to do the steps described in section Accessing PersistenceService. After that you can get the instance of CdsDataStore using persistence.getCdsDataStore() method.

Select query =  ...; // construct the query

CdsDataStore cdsDataStore = persistence.getCdsDataStore();
cdsDataStore.execute(query);