Run Executable JAR Files on SAP BTP
Introduction
I received a requirement to develop an interface for payment files with a bank as part of an S/4HANA implementation project. This project involved migrating from the existing SAP ECC6 on-premise + SAP PI/PO setup to S/4HANA Cloud, Private Edition + SAP BTP-CPI.
The payment files needed to be encrypted or hashed using the bank’s proprietary executable JAR files before being sent. Here’s a brief overview of the requirements:
Bank A: Encrypt files using unknow algorithm (likely AES) with the following command:
Bank B: Hash files using unknow algorithm (not MD5) with the following command:
In the previous setup, SAP PI/PO handled this task by running OS commands on the communication channel. However, with the new cloud-based solution, I couldn’t use standard Java libraries and had to rely on the bank’s provided executable JAR files.
Challenges
Running executable JAR files in a cloud-based solution like S/4HANA Cloud + SAP CPI posed several challenges. Here are the solutions I tried and why they didn’t work:
Solution 1: Run on S/4HANA with AL11 Shared Drive
Problem:S/4HANA Cloud does not allow installing a JRE.OS commands like java -jar … cannot be executed on the AL11 shared drive.
Solution 2: Run on the User’s Desktop
Problem:SAP GUI is not allowed on user desktops.OS commands cannot be executed via Web GUI.
Solution 3: Run via SAP CPI
Problem:Executable JAR files cannot be run directly in SAP CPI iFlows.
After trying these approaches, I found a workaround that worked in my case.
Solution: Wrap and Run the Executable JAR in a Custom Java Application on SAP BTP
I developed a Java program to wrap and run the executable JAR files, then deployed it on SAP BTP. To simplify the implementation, I used Spring Boot.
Here’s an overview of the architecture and development steps:
Create a Spring Boot application to wrap the executable JAR files provided by the bank. Use ProcessBuilder to execute the JAR files.Read and write intermediate files by encoding and decoding them as base64 strings. These files are processed using the application container’s ephemeral storage.Expose the application as a REST API to accept and return base64-encoded payment files.Deploy the application on SAP BTP Cloud Foundry as a Java application.Secure the API using SAP XSUAA services.Integrate with SAP S/4HANA via CPI iFlows.Develop an ABAP program to handle base64 (xstring) files on AL11.
In this blog, I’ll focus on Steps 1-4 and demonstrate the solution using a mock JAR file to simulate the scenario.
Implementation Details
Step 0: Simulate the Bank’s Executable JAR
To simulate the bank’s JAR file, I created a mock JAR file. Running the following command processes an input file and generates an encrypted output file in the same folder:
Example Code (BankExecutableMock.java)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class BankExecutableMock {
public static void main(String[] args) {
if (args.length < 1) {
System.err.println(“Usage: java BankExecutableMock <inputFile>”);
return;
}
String inputFilePath = args[0];
String outputFilePath = getOutputFileName(inputFilePath);
// Read content from the input file
String fileContent = readFromFile(inputFilePath);
if (fileContent == null) {
System.err.println(“Failed to read from the input file.”);
return;
}
// Process the content
String processedContent = “Encrypted((“ + fileContent.trim() + “))”;
// Write processed content to the output file
writeToFile(outputFilePath, processedContent);
}
public static String getOutputFileName(String inputFileName) {
// Replace the extension with “.txt”
return inputFileName.replaceAll(“\.\w+$”, “.encrypt”);
}
public static void writeToFile(String filePath, String content) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
writer.write(content);
System.out.println(“Content written to file successfully: “ + filePath);
} catch (IOException e) {
System.err.println(“Error writing to file: “ + e.getMessage());
}
}
public static String readFromFile(String filePath) {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append(System.lineSeparator());
}
} catch (IOException e) {
System.err.println(“Error reading from file: “ + e.getMessage());
return null;
}
return content.toString();
}
}
Example Input File (input.txt)
Example Result File (input.encrypt)
Step 1: Develop the Wrapper Application
Use Spring Initializr to create a Spring Boot application. Inside the application, use ProcessBuilder to run the executable JAR.
Create a Spring Boot Application
Generate a Spring Boot project with the following dependencies:
Spring WebSpring SecurityOAuth2 Resource ServerSpring Boot Actuator (optional, for monitoring)
After creating the Spring project, extract the files and copy bank-executable-mock.jar into the src/resources folder. Initially, comment out security-related dependencies in pom.xml to skip authentication during testing.
Example Code (pom.xml):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!– Security dependencies (commented for now) –>
<!– Uncomment these after testing –>
</dependencies>
Create a new controller class to receive HTTP requests as shown below.
Example Code (EncryptionController.java)
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.*;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping(“/api”)
public class EncryptionController {
@PostMapping(“/encrypt”)
public ResponseEntity<Map<String, String>> encryptFile(
@RequestBody Map<String, String> request) {
try {
String rootPath = “C:/Dev/Bank”;
String javaPath = “java”;
// Extract input fields
String inputFileName = request.get(“input_file”);
String base64Input = request.get(“input_data”);
if (inputFileName == null || inputFileName.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of(“error”, “Input field ‘input_file’ is missing or empty.”));
}
if (base64Input == null || base64Input.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of(“error”, “Input field ‘input_data’ is missing or empty.”));
}
// Generate unique folder for the request
String uniqueFolderName = UUID.randomUUID().toString();
Path uniqueFolderPath = Paths.get(rootPath, uniqueFolderName);
Files.createDirectories(uniqueFolderPath);
// Decode Base64 input
byte[] decodedBytes = Base64.getDecoder().decode(base64Input);
// Write input file with UTF-8
Path inputFilePath = uniqueFolderPath.resolve(inputFileName);
writeUtf8(inputFilePath, decodedBytes);
// Define output file path with dynamic extension
String outputFileName = getOutputFileName(inputFileName);
Path outputFilePath = uniqueFolderPath.resolve(outputFileName);
// Run the external JAR to process the file
ProcessBuilder processBuilder = new ProcessBuilder(javaPath, “-jar”, getJarPath(),
inputFilePath.toString());
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
return ResponseEntity.status(500)
.body(Map.of(“error”, “Encryption process failed.”));
}
// Read the output file and encode it to Base64
byte[] encryptedFileBytes = Files.readAllBytes(outputFilePath);
String base64Encrypted = Base64.getEncoder().encodeToString(encryptedFileBytes);
// Clean up the folder
// cleanUpDirectory(uniqueFolderPath);
// Return the response in the specified format
return ResponseEntity
.ok(Map.of(“output_file”, outputFileName, “output_data”, base64Encrypted));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(500).body(Map.of(“error”, e.getMessage()));
}
}
private String getOutputFileName(String inputFileName) {
// Replace the extension with “.txt”
return inputFileName.replaceAll(“\.\w+$”, “.encrypt”);
}
private String getJarPath() throws Exception {
// Extract the bundled JAR to a temporary location
InputStream jarStream = getClass().getResourceAsStream(“/bank-executable-mock.jar”);
if (jarStream == null) {
throw new FileNotFoundException(“Bundled JAR not found.”);
}
Path tempJarPath = Files.createTempFile(“bank-executable-mock”, “.jar”);
Files.copy(jarStream, tempJarPath, StandardCopyOption.REPLACE_EXISTING);
tempJarPath.toFile().deleteOnExit();
return tempJarPath.toString();
}
private void writeUtf8(Path filePath, byte[] content) throws Exception {
// Write UTF-8
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(filePath), StandardCharsets.UTF_8))) {
// Write content
writer.write(new String(content, StandardCharsets.UTF_8));
}
}
private void cleanUpDirectory(Path directoryPath) throws Exception {
// Recursively delete files and the directory
Files.walk(directoryPath).sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
Step 2: Run Locally
Encode files as base64 strings to process them, then run the Spring Boot application on your local machine and test it using Curl/Postman.
URL
Request
“input_file”: “input.txt“,
“input_data”: “VGhpcyBpcyBteSBwYXltZW50IGZpbGUu“
}
Response
“output_file”: “input.encrypt“,
“output_data”: “RW5jcnlwdGVkKChUaGlzIGlzIG15IHBheW1lbnQgZmlsZS4pKQ“
}
You can also verify the results by checking the temporary files in the generated folder.
Step 3: Handle File Storage and Correct Java Runtime Path
Before deploying to BTP Cloud Foundry, update the code as follows:
Change the Java runtime to the full path within the Cloud Foundry environment.Switch file storage from the local machine to ephemeral storage within the application container.Optionally, uncomment the method cleanUpDirectory() to clean up temporary files after processing.
Example Code (EncryptionController.java)
// Old paths
// String rootPath = “C:/Dev/Bank”;
// String javaPath = “java”;
// Updated paths for SAP BTP
String rootPath = “/tmp”;
String javaPath = “/home/vcap/app/META-INF/.sap_java_buildpack/sap_machine_jdk/bin/java”;
// Uncomment to clean up the folder after processing
cleanUpDirectory(uniqueFolderPath);
Step 4: Deploy on SAP BTP
Modify the application to support deployment on SAP BTP, and create a manifest.yml file.
Example Code (manifest.yml)
– name: bank-wrapper
path: ./target/bank-wrapper-0.0.1-SNAPSHOT.jar
random-route: true
memory: 1024M
buildpacks:
– sap_java_buildpack_jakarta
env:
TARGET_RUNTIME: tomcat
JBP_CONFIG_COMPONENTS: “jres: [‘com.sap.xs.java.buildpack.jdk.SAPMachineJDK’]“
JBP_CONFIG_SAP_MACHINE_JDK: ‘{ version: 21.+ }‘
Deployment Commands
cf api https://api.cf.us10-001.hana.ondemand.com
cf login –sso
cf push
Test the API after deployment to verify it behaves as expected.
If the Java path is not updated, the following error may occur:
“error”: “Cannot run program “java”: error=2, No such file or directory”
}
Step 5: Secure the Application (Optional)
Enable security capabilities by integrating XSUAA.
Example Code (xs-security.json)
“xsappname”: “bank-wrapper“,
“tenant-mode”: “dedicated“,
“scopes”: [
{
“name”: “$XSAPPNAME.api_access“,
“description”: “Access to the API“
}
],
“role-templates”: [
{
“name”: “API_ACCESS“,
“description”: “Role for API access“,
“scope-references”: [
“$XSAPPNAME.api_access“
]
}
],
“oauth2-configuration”: {
“redirect-uris”: [
“https://*.cfapps.us10.hana.ondemand.com/**“
]
}
}
Example Code (SecurityConfig.java)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
auth -> auth.requestMatchers(“/api/*”).authenticated().anyRequest().permitAll())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
String jwkSetUri = “https://[your sub account].authentication.us10.hana.ondemand.com/token_keys”;
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
}
}
Update manifest.yml to include the XSUAA service:
– name: bank-wrapper
path: ./target/bank-wrapper-0.0.1-SNAPSHOT.jar
random-route: true
memory: 1024M
buildpacks:
– sap_java_buildpack_jakarta
env:
TARGET_RUNTIME: tomcat
JBP_CONFIG_COMPONENTS: “jres: [‘com.sap.xs.java.buildpack.jdk.SAPMachineJDK’]“
JBP_CONFIG_SAP_MACHINE_JDK: ‘{ version: 21.+ }‘
services:
– bank-xsuaa-instance
Deployment Command:
cf create-service xsuaa application bank-xsuaa-instance -c xs-security.json
cf create-service-key bank-xsuaa-instance defaultKey
# (optional) check service key
cf service-key bank-xsuaa-instance defaultKey
# Deploy application
cf push
cf bind-service bank-wrapper bank-xsuaa-instance
cf restage bank-wrapper
Now, the API is secured with OAuth 2.0 by using Bearer {{token}}.
Conclusion
This approach allowed me to handle executable JAR files in a cloud-based environment on SAP BTP while addressing challenges like file storage and security. I hope this guide helps other developers facing similar requirements.
Run Executable JAR Files on SAP BTPIntroductionI received a requirement to develop an interface for payment files with a bank as part of an S/4HANA implementation project. This project involved migrating from the existing SAP ECC6 on-premise + SAP PI/PO setup to S/4HANA Cloud, Private Edition + SAP BTP-CPI.The payment files needed to be encrypted or hashed using the bank’s proprietary executable JAR files before being sent. Here’s a brief overview of the requirements:Bank A: Encrypt files using unknow algorithm (likely AES) with the following command:java -jar Bank-A-Encrypt.jar <input-file-full-path> <output-file-full-path> Bank B: Hash files using unknow algorithm (not MD5) with the following command:java -Dcustom_config.properties=”./config/custom_config.properties” -jar Bank-B-Hash.jar <input-file-full-path> <output-file-full-path> In the previous setup, SAP PI/PO handled this task by running OS commands on the communication channel. However, with the new cloud-based solution, I couldn’t use standard Java libraries and had to rely on the bank’s provided executable JAR files.ChallengesRunning executable JAR files in a cloud-based solution like S/4HANA Cloud + SAP CPI posed several challenges. Here are the solutions I tried and why they didn’t work:Solution 1: Run on S/4HANA with AL11 Shared DriveProblem:S/4HANA Cloud does not allow installing a JRE.OS commands like java -jar … cannot be executed on the AL11 shared drive.Solution 2: Run on the User’s DesktopProblem:SAP GUI is not allowed on user desktops.OS commands cannot be executed via Web GUI.Solution 3: Run via SAP CPIProblem:Executable JAR files cannot be run directly in SAP CPI iFlows.After trying these approaches, I found a workaround that worked in my case.Solution: Wrap and Run the Executable JAR in a Custom Java Application on SAP BTPI developed a Java program to wrap and run the executable JAR files, then deployed it on SAP BTP. To simplify the implementation, I used Spring Boot.Here’s an overview of the architecture and development steps:Create a Spring Boot application to wrap the executable JAR files provided by the bank. Use ProcessBuilder to execute the JAR files.Read and write intermediate files by encoding and decoding them as base64 strings. These files are processed using the application container’s ephemeral storage.Expose the application as a REST API to accept and return base64-encoded payment files.Deploy the application on SAP BTP Cloud Foundry as a Java application.Secure the API using SAP XSUAA services.Integrate with SAP S/4HANA via CPI iFlows.Develop an ABAP program to handle base64 (xstring) files on AL11.In this blog, I’ll focus on Steps 1-4 and demonstrate the solution using a mock JAR file to simulate the scenario.Implementation DetailsStep 0: Simulate the Bank’s Executable JARTo simulate the bank’s JAR file, I created a mock JAR file. Running the following command processes an input file and generates an encrypted output file in the same folder:java -jar bank-executable-mock.jar <input-file-full-path> Example Code (BankExecutableMock.java)package com.binlah.sap.btp.appdev;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class BankExecutableMock {
public static void main(String[] args) {
if (args.length < 1) {
System.err.println(“Usage: java BankExecutableMock <inputFile>”);
return;
}
String inputFilePath = args[0];
String outputFilePath = getOutputFileName(inputFilePath);
// Read content from the input file
String fileContent = readFromFile(inputFilePath);
if (fileContent == null) {
System.err.println(“Failed to read from the input file.”);
return;
}
// Process the content
String processedContent = “Encrypted((” + fileContent.trim() + “))”;
// Write processed content to the output file
writeToFile(outputFilePath, processedContent);
}
public static String getOutputFileName(String inputFileName) {
// Replace the extension with “.txt”
return inputFileName.replaceAll(“\.\w+$”, “.encrypt”);
}
public static void writeToFile(String filePath, String content) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
writer.write(content);
System.out.println(“Content written to file successfully: ” + filePath);
} catch (IOException e) {
System.err.println(“Error writing to file: ” + e.getMessage());
}
}
public static String readFromFile(String filePath) {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append(System.lineSeparator());
}
} catch (IOException e) {
System.err.println(“Error reading from file: ” + e.getMessage());
return null;
}
return content.toString();
}
} Example Input File (input.txt)This is my payment file. Example Result File (input.encrypt)Encrypted((This is my payment file.)) Step 1: Develop the Wrapper ApplicationUse Spring Initializr to create a Spring Boot application. Inside the application, use ProcessBuilder to run the executable JAR.Create a Spring Boot ApplicationGenerate a Spring Boot project with the following dependencies:Spring WebSpring SecurityOAuth2 Resource ServerSpring Boot Actuator (optional, for monitoring)After creating the Spring project, extract the files and copy bank-executable-mock.jar into the src/resources folder. Initially, comment out security-related dependencies in pom.xml to skip authentication during testing.Example Code (pom.xml):<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!– Security dependencies (commented for now) –>
<!– Uncomment these after testing –>
</dependencies> Create a new controller class to receive HTTP requests as shown below.Example Code (EncryptionController.java)package com.binlah.sap.btp.appdev.bank_wrapper;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.*;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping(“/api”)
public class EncryptionController {
@PostMapping(“/encrypt”)
public ResponseEntity<Map<String, String>> encryptFile(
@RequestBody Map<String, String> request) {
try {
String rootPath = “C:/Dev/Bank”;
String javaPath = “java”;
// Extract input fields
String inputFileName = request.get(“input_file”);
String base64Input = request.get(“input_data”);
if (inputFileName == null || inputFileName.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of(“error”, “Input field ‘input_file’ is missing or empty.”));
}
if (base64Input == null || base64Input.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of(“error”, “Input field ‘input_data’ is missing or empty.”));
}
// Generate unique folder for the request
String uniqueFolderName = UUID.randomUUID().toString();
Path uniqueFolderPath = Paths.get(rootPath, uniqueFolderName);
Files.createDirectories(uniqueFolderPath);
// Decode Base64 input
byte[] decodedBytes = Base64.getDecoder().decode(base64Input);
// Write input file with UTF-8
Path inputFilePath = uniqueFolderPath.resolve(inputFileName);
writeUtf8(inputFilePath, decodedBytes);
// Define output file path with dynamic extension
String outputFileName = getOutputFileName(inputFileName);
Path outputFilePath = uniqueFolderPath.resolve(outputFileName);
// Run the external JAR to process the file
ProcessBuilder processBuilder = new ProcessBuilder(javaPath, “-jar”, getJarPath(),
inputFilePath.toString());
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
return ResponseEntity.status(500)
.body(Map.of(“error”, “Encryption process failed.”));
}
// Read the output file and encode it to Base64
byte[] encryptedFileBytes = Files.readAllBytes(outputFilePath);
String base64Encrypted = Base64.getEncoder().encodeToString(encryptedFileBytes);
// Clean up the folder
// cleanUpDirectory(uniqueFolderPath);
// Return the response in the specified format
return ResponseEntity
.ok(Map.of(“output_file”, outputFileName, “output_data”, base64Encrypted));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(500).body(Map.of(“error”, e.getMessage()));
}
}
private String getOutputFileName(String inputFileName) {
// Replace the extension with “.txt”
return inputFileName.replaceAll(“\.\w+$”, “.encrypt”);
}
private String getJarPath() throws Exception {
// Extract the bundled JAR to a temporary location
InputStream jarStream = getClass().getResourceAsStream(“/bank-executable-mock.jar”);
if (jarStream == null) {
throw new FileNotFoundException(“Bundled JAR not found.”);
}
Path tempJarPath = Files.createTempFile(“bank-executable-mock”, “.jar”);
Files.copy(jarStream, tempJarPath, StandardCopyOption.REPLACE_EXISTING);
tempJarPath.toFile().deleteOnExit();
return tempJarPath.toString();
}
private void writeUtf8(Path filePath, byte[] content) throws Exception {
// Write UTF-8
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(filePath), StandardCharsets.UTF_8))) {
// Write content
writer.write(new String(content, StandardCharsets.UTF_8));
}
}
private void cleanUpDirectory(Path directoryPath) throws Exception {
// Recursively delete files and the directory
Files.walk(directoryPath).sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (Exception e) {
e.printStackTrace();
}
});
}
} Step 2: Run LocallyEncode files as base64 strings to process them, then run the Spring Boot application on your local machine and test it using Curl/Postman.URLhttp://localhost:8080/api/encrypt Request{
“input_file”: “input.txt”,
“input_data”: “VGhpcyBpcyBteSBwYXltZW50IGZpbGUu”
} Response{
“output_file”: “input.encrypt”,
“output_data”: “RW5jcnlwdGVkKChUaGlzIGlzIG15IHBheW1lbnQgZmlsZS4pKQ”
} You can also verify the results by checking the temporary files in the generated folder.Step 3: Handle File Storage and Correct Java Runtime PathBefore deploying to BTP Cloud Foundry, update the code as follows:Change the Java runtime to the full path within the Cloud Foundry environment.Switch file storage from the local machine to ephemeral storage within the application container.Optionally, uncomment the method cleanUpDirectory() to clean up temporary files after processing.Example Code (EncryptionController.java)// Modify file paths and Java runtime as shown below
// Old paths
// String rootPath = “C:/Dev/Bank”;
// String javaPath = “java”;
// Updated paths for SAP BTP
String rootPath = “/tmp”;
String javaPath = “/home/vcap/app/META-INF/.sap_java_buildpack/sap_machine_jdk/bin/java”;
// Uncomment to clean up the folder after processing
cleanUpDirectory(uniqueFolderPath); Step 4: Deploy on SAP BTPModify the application to support deployment on SAP BTP, and create a manifest.yml file.Example Code (manifest.yml)applications:
– name: bank-wrapper
path: ./target/bank-wrapper-0.0.1-SNAPSHOT.jar
random-route: true
memory: 1024M
buildpacks:
– sap_java_buildpack_jakarta
env:
TARGET_RUNTIME: tomcat
JBP_CONFIG_COMPONENTS: “jres: [‘com.sap.xs.java.buildpack.jdk.SAPMachineJDK’]”
JBP_CONFIG_SAP_MACHINE_JDK: ‘{ version: 21.+ }’ Deployment Commandsmvn clean install
cf api https://api.cf.us10-001.hana.ondemand.com
cf login –sso
cf push Test the API after deployment to verify it behaves as expected.If the Java path is not updated, the following error may occur:{
“error”: “Cannot run program “java”: error=2, No such file or directory”
} Step 5: Secure the Application (Optional)Enable security capabilities by integrating XSUAA.Example Code (xs-security.json){
“xsappname”: “bank-wrapper”,
“tenant-mode”: “dedicated”,
“scopes”: [
{
“name”: “$XSAPPNAME.api_access”,
“description”: “Access to the API”
}
],
“role-templates”: [
{
“name”: “API_ACCESS”,
“description”: “Role for API access”,
“scope-references”: [
“$XSAPPNAME.api_access”
]
}
],
“oauth2-configuration”: {
“redirect-uris”: [
“https://*.cfapps.us10.hana.ondemand.com/**”
]
}
} Example Code (SecurityConfig.java)package com.binlah.sap.btp.appdev.bank_wrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
auth -> auth.requestMatchers(“/api/*”).authenticated().anyRequest().permitAll())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
String jwkSetUri = “https://[your sub account].authentication.us10.hana.ondemand.com/token_keys”;
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
}
} Update manifest.yml to include the XSUAA service: applications:
– name: bank-wrapper
path: ./target/bank-wrapper-0.0.1-SNAPSHOT.jar
random-route: true
memory: 1024M
buildpacks:
– sap_java_buildpack_jakarta
env:
TARGET_RUNTIME: tomcat
JBP_CONFIG_COMPONENTS: “jres: [‘com.sap.xs.java.buildpack.jdk.SAPMachineJDK’]”
JBP_CONFIG_SAP_MACHINE_JDK: ‘{ version: 21.+ }’
services:
– bank-xsuaa-instance Deployment Command:mvn clean install
cf create-service xsuaa application bank-xsuaa-instance -c xs-security.json
cf create-service-key bank-xsuaa-instance defaultKey
# (optional) check service key
cf service-key bank-xsuaa-instance defaultKey
# Deploy application
cf push
cf bind-service bank-wrapper bank-xsuaa-instance
cf restage bank-wrapper Now, the API is secured with OAuth 2.0 by using Bearer {{token}}.https://[your sub account].authentication.us10.hana.ondemand.com/oauth/token ConclusionThis approach allowed me to handle executable JAR files in a cloud-based environment on SAP BTP while addressing challenges like file storage and security. I hope this guide helps other developers facing similar requirements. Read More Technology Blogs by Members articles
#SAP
#SAPTechnologyblog