Technical Articles
Step-by-Step: Consume REST Service API with SAP Cloud SDK
Disclaimer: This blog post is only applicable for the SAP Cloud SDK version of at most 2.19.2. We plan to continuously migrate these blog posts into our List of Tutorials. Feel free to check out our updated Tutorials on the SAP Cloud SDK.
Note: This post is part of a series. For a complete overview visit the SAP Cloud SDK Overview.
Introduction
We have already seen the SAP Cloud SDK providing capabilities for conveniently consuming OData and SOAP based API services. But some application may target a web service, which serves resources from an REST based endpoint, instead of OData or SOAP. If the REST based endpoint is well-defined, its specification can be declared as OpenAPI.
In this step-by-step guide we will choose a RESTful API from the SAP API Business Hub, take the attached OpenAPI interface file and generate Java code from it. By using the latest Maven project archetype of SAP Cloud SDK, we will have a well prepared start to implement and deploy a new application to the SCP, capable of consuming an OpenAPI service. We will setup a destination to the API service to test and proof the new application.
In the upcoming steps, we will use the Monitoring v2 service on SCP Neo. It provides up-to-date runtime metrics to connected applications and processes. Its API will be consumed by a newly created application running on SCP Cloud Foundry. However, the general strategy of this guide can be used for either platform deployment.
Prerequisites:
We assume you have completed the tutorial “HelloWorld on SCP CloudFoundry”, such that the following requirements are already met:
- You have an SCP account, which is able to deploy simple Java web applications.
- Java, Maven and an IDE of your choice are installed and up-to-date.
- Cloud Foundry command line interface is installed, such that you can run
cf version
without an error.
Recommendations:
If you are new to the SAP Cloud SDK, you may want to start on the Cloud SDK Overview page. For an introduction on OpenAPI and SAP Cloud SDK, we recommend the post Access any REST service with Cloud SDK. Find more courses and guides in the official tutorials section.
Note: This tutorial does not require access to an SAP S/4HANA system.
Build an application powered by OpenAPI and Cloud SDK
Steps:
- Choose a REST service
- Build a new Spring application
- Enable OpenAPI code generation
- Run the code generation
- Setup the application
- Setup and run a mock server test
- Obtain credentials from SCP Neo
- Prepare configuration on SCP Cloud Foundry
- Deploy application
- Test
Step 1: Choose a service and download the OpenAPI interface file
In the beginning it is important to make sure the web service, which will be consumed, is actually offering an OpenAPI interface file. We need a JSON or YAML file, as a formal specification of the API. For this step-by-step guide we assume the “Monitoring v2” as target API.
Excursus: Explore a REST Service on SAP API Business Hub
We will now browse and download an OpenAPI file for “Monitoring v2”.
- Visit the SAP API Business Hub.
Its services are declared as either ODATA, SOAP or REST. If you are interested in OData and SOAP, please take a look at what the SAP Cloud SDK can provide as additional functionality.
- Search for “Monitoring v2” and click on the respective row.
On this API reference, you can find a documentation of multiple interactive endpoints. By exploring the colored fields, you can test the API and investigate request parameters and expected response bodies. With this Swagger UI -like interface you can test API calls against a sandbox system.
- Change to the “Details” page.
Here you can find meta information about an API, as well as available URLs of the API. They heavily depend on your target landscape. Also details about authentication and links to in-depth documentation are referenced on this page.
- Click the “Download Specification” button
This will trigger the download of the OpenAPI file to this API. The specification can be saved as JSON or YAML. Both of them can be parsed later by the code generator. Let’s choose YAML.
Step 2: Create a new Spring application
In order to quickly get started with a new web application project, let’s use the Spring Boot archetype prepared by SAP Cloud SDK. Open the command line and run the following Maven command to create a convenient multi-module project at the current directory:
mvn archetype:generate -DarchetypeGroupId=com.sap.cloud.s4hana.archetypes -DarchetypeArtifactId=scp-cf-spring -DarchetypeVersion=RELEASE
Note: Please change the RELEASE
reference at the end of the command to your preferred version. This tutorial uses org.example.rest
as base package.
A project based on the Spring Boot archetype is generated, containing the following elements:
- ./
- application/
- cx-server/
- integration-tests/
- uni-tests/
- pom.xml
If you are further interested in the details of the project structure, please find the related tutorial with in-depth information on the similar TomEE archetype.
Step 3: Enable OpenAPI code generation
- To enable the automatic code generation with OpenAPI, please rename and move the previously downloaded YAML file into the folder:
./application/src/main/resources/api.yaml
- Open the
./application/pom.xml
file and add the following<plugin>
next to the other declared plugins.<plugins> ... <plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>3.3.4</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec> <generatorName>java</generatorName> <configurationFile>${project.basedir}/src/main/resources/api-options.json</configurationFile> <generateApiTests>false</generateApiTests> <generateModelTests>false</generateModelTests> <generateApiDocumentation>false</generateApiDocumentation> <generateModelDocumentation>false</generateModelDocumentation> </configuration> </execution> </executions> </plugin> </plugins>
Note: You can easily change and update the plugin version to a recent state. Since this tool is still in ongoing development and gets improved on a regularly basis, we recommend using the latest version. You can find it in the Maven central repository.
- For later compilation of the generated code, an additional dependency needs to be declared in the same
pom.xml
file. Again, please feel encouraged to replace the version with the latest version from the Maven central repository.<dependencies> ... <!-- due to annotations in the generated code --> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.22</version> </dependency> </dependencies>
- You may have already noticed the reference of
/src/main/resources/api-options.json
. In order to improve the readability of the code generator setup, we delegate some settings to a separate JSON file. Create theapi-options.json
with the following content, and put it next toapi.yaml
:{ "java8" : true, "dateLibrary" : "java8", "modelPackage" : "org.example.rest.monitoring.model", "apiPackage" : "org.example.rest.monitoring.api", "invokerPackage" : "org.example.rest.monitoring.invoker", "serializableModel" : true, "withXml" : false, "booleanGetterPrefix" : "is", "useRuntimeException" : false, "hideGenerationTimestamp" : true, "library" : "resttemplate", "sourceFolder" : "/src/main/java" }
You can modify it to your needs. But we recommend leaving the value
"library" : "resttemplate"
intact, to ensure support with SAP Cloud SDK.
Your application module will now look like this – with the new files highlighted:
Once the modifications are done, we are ready to start the code generation.
Step 4: Run the code generation
- Use the command line again, to execute the following Maven command:
mvn generate-sources
This phase will traverse the project module hierarchy and eventually starts the code generator in application.
- Once the Maven process finishes, you can find the generated code in the
target
directory of theapplication
module:./application/target/generated-sources/openapi/src/main/java/
Depending on the library template chosen in the generator settings, the generated code will differ both, for API and invoker classes.
Note: Make sure that your IDE identifies the files as generated sources, such that the classes can be resolved from your editors classpath.
- If you find your generated API methods having incorrect letter casing, it is likely due to an improper API naming convention from the API file. You could either fix the naming manually in the original API file or use an automatic routine to fix the specification during Maven build. Although we do not recommend manually changing generated code, you could still fix the issue by adjusting the values for
operationId
elements in theapi.yaml
. We advise using an automatic approach to handle the issue, please see the Appendix in the adjacent post Access any REST service with SAP Cloud SDK.
Your application module will now have generated sources:
Take a look into the Java files of the generated packages:
api
contains a dynamic set of classes, depending on the OpenAPI interface. Each OpenAPI endpoint is mapped to a class, which can be instantiated. Given the interface, each API class features the respective operations as methods. The return type of these methods are defined in themodel
package.invoker
holds static classes for API querying, e.g.ApiClient
and helper classes.model
incorporates classes, which are mapped to entities defined by the API. They enable the type-safe usage of API responses.
Step 5: Setup the application
- Prepare a dedicated Destination type
Create a new java classMonitoringDestination.java
to serve as placeholder for the destination identifier.package org.example.rest; import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationDeclarator; public class MonitoringDestination extends DestinationDeclarator { public final static String DESTINATION_NAME = "MonitoringEndpoint"; public MonitoringDestination() { super(DESTINATION_NAME); } }
- Implement the OpenAPI related application beans
Create a new classConfigurationMonitoring.java
to serve beans with request scope, to ensure tenant and user separation. With theMonitoringDestination
reference we can resolve service paths and theHttpClient
, which automatically resolves authorization headers for us.package org.example.rest; import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor; import org.apache.http.client.HttpClient; import org.example.rest.monitoring.invoker.ApiClient; import org.springframework.context.annotation.*; import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.WebApplicationContext; import java.net.URI; import java.net.URISyntaxException; @Configuration public class ConfigurationMonitoring { @Bean @Primary @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public ApiClient createApiClient() throws URISyntaxException { // resolve destination final Destination destination = DestinationAccessor.getDestination(MonitoringDestination.DESTINATION_NAME); // instantiate RestTemplate and ApiClient final RestTemplate restTemplate = createRestTemplate(); final ApiClient apiClient = new ApiClient(restTemplate); // set root of API Client base path final URI uri = destination.getUri(); final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null); apiClient.setBasePath(path.toString()); return apiClient; } @Bean @Primary @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public RestTemplate createRestTemplate() { // create new HttpClient for destination final HttpClient httpClient = HttpClientAccessor.getHttpClient(MonitoringDestination.DESTINATION_NAME); // instantiate template with prepared HttpClient, featuring repeated response reading final RestTemplate restTemplate = new RestTemplate(); final HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); httpRequestFactory.setHttpClient(httpClient); restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory)); return restTemplate; } }
With
@Primary
we signal Spring to select this instantiation method with priority over the methods provided by default from the generated OpenAPI code.Note: If you plan to not use the bean or need access in a threaded environment (e.g. Hysterics commands), then you can just call the methods directly.
- Implement a response model
Create a new classmodels/MonitorResponse.java
to hold the values prepared by the controller.package org.example.rest.models; import com.fasterxml.jackson.annotation.JsonProperty; import javax.annotation.Nonnull; import org.example.rest.monitoring.model.AccountsAppsMetricsresponse; import java.util.List; public class MonitorResponse { @JsonProperty("monitor") private final List<AccountsAppsMetricsresponse> monitor; public MonitorResponse( @Nonnull final List<AccountsAppsMetricsresponse> monitor ) { this.monitor = monitor; } }
- Implement a service controller
Create a new classcontrollers/MonitorController.java
to listen on requests to our application. To keep the example simple, we are going to simply wrap the API response into our own model classMonitorResponse
. You can later manipulate theresult
list and or use a different response model for further data processing.package org.example.rest.controllers; import javax.annotation.Nonnull; import org.example.rest.models.MonitorResponse; import org.example.rest.monitoring.api.JavaApplicationMetricsApi; import org.example.rest.monitoring.invoker.ApiClient; import org.example.rest.monitoring.model.AccountsAppsMetricsresponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.net.URISyntaxException; import java.util.List; @RestController @RequestMapping("/monitor") public class MonitorController { private final ApiClient apiClient; public MonitorController( @Nonnull final ApiClient apiClient ) { this.apiClient = apiClient; } @RequestMapping(method = RequestMethod.GET) public ResponseEntity<MonitorResponse> getMonitor(@RequestParam final String account, @RequestParam final String app) throws URISyntaxException { final List<AccountsAppsMetricsresponse> result = new JavaApplicationMetricsApi(apiClient).getAccountsSubaccountNameAppsAppNameMetrics(account, app); return ResponseEntity.ok(new MonitorResponse(result)); } }
As you see, our controller is listening for a
GET /monitor
request. Upon execution an instance ofApiClient
is injected as part of the class constructor. Spring will resolve the bean from our previously introducedConfigurationMonitoring
configuration.
Your application module now contains classes for controller and model:
Step 6: Setup and run a mock server test
It’s time to verify the newly created code with an elaborate mock server test. We are now working in the integration-tests
module.
- Create a new resource file
./integration-tests/src/test/resources/mocked_monitoring_response.json
The following JSON payload is going to be used as mocked server response. Please note, while this example only features three metrics, in reality there are many more records for a given process.
[ { "account": "d012345trial", "application": "sampleapp", "state": "Ok", "processes": [ { "process": "0123456789abcdef", "state": "Ok", "metrics": [ { "name": "Used Disc Space", "state": "Ok", "value": 57, "unit": "%", "warningThreshold": 90, "errorThreshold": 95, "timestamp": 1551950283000, "output": "DISK OK - free space: / 3041 MB (39% inode=79%); /var 1459 MB (76% inode=98%); /tmp 1844 MB (96% inode=99%);", "metricType": "rate", "min": 0, "max": 8063 }, { "name": "Requests per Minute", "state": "Ok", "value": 0, "unit": "requests", "warningThreshold": 0, "errorThreshold": 0, "timestamp": 1551950284000, "output": "JMX OK - RequestsCountMin = 0 ", "metricType": "performance", "min": 0, "max": 0 }, { "name": "CPU Load", "state": "Ok", "value": 10, "unit": "%", "warningThreshold": 80, "errorThreshold": 90, "timestamp": 1551950283000, "output": "OK CPUValue: 10 (W> 80, C> 90) ", "metricType": "performance", "min": 0, "max": 0 } ] } ] } ]
- Create a new test file
./integration-tests/src/test/java/[...]/MonitoringControllerLocalTest.java
The following test class currently contains a test, whether the contents of the OpenAPI endpoint is correctly wrapped and forwarded by our controller.
package org.example.rest; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextExecutor; import com.sap.cloud.sdk.testutil.MockUtil; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.io.IOException; import java.net.URI; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.lang.Thread.currentThread; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith( SpringRunner.class ) @WebMvcTest public class MonitoringControllerLocalTest { private static final String TEST_API_BASE_PATH = "/monitoring/v2"; private static final String TEST_SCP_ACCOUNT_ID = "d012345trial"; private static final String TEST_SCP_APPLICATION = "sampleapp"; private static final MockUtil mockUtil = new MockUtil(); @Rule public final WireMockRule wireMockServer = new WireMockRule(wireMockConfig().dynamicPort()); @Autowired private MockMvc mvc; private static String TEST_API_RESPONSE; @BeforeClass public static void beforeClass() throws IOException { mockUtil.mockDefaults(); // load expected response from mocked API service TEST_API_RESPONSE = IOUtils.toString(currentThread().getContextClassLoader().getResourceAsStream("mocked_monitoring_response.json"), Charsets.UTF_8); } @Before public void mockServerResponses() { stubFor( get(urlPathMatching(TEST_API_BASE_PATH + "/accounts/(\\w+)/apps/(\\w+)/metrics")) .willReturn(okJson(TEST_API_RESPONSE))); } @Test public void testMonitor() throws Exception { final URI apiUrl = new URI(wireMockServer.baseUrl() + "/" + TEST_API_BASE_PATH); mockUtil.mockDestination(MonitoringDestination.DESTINATION_NAME, apiUrl, null); new RequestContextExecutor().execute(() -> { mvc.perform(MockMvcRequestBuilders.get("/monitor").param("account", TEST_SCP_ACCOUNT_ID).param("app", TEST_SCP_APPLICATION)) .andExpect(status().isOk()) .andExpect(content().json("{\"monitor\":"+TEST_API_RESPONSE+"}")); }); } }
Note: You can easily modify the chain of
.andExpect(...)
statements to improve test assertions.During the test the following things happen:
- Since the test is run with
SpringRunner
and annotated with@WebMvcTest
we can use the autowiredMockMvc
instance to directly call our controller. - Our controller makes an HTTP request to the mock server with
GET /monitoring/v2/accounts/d012345trial/apps/sample-application/metrics
- The controller response is checked for an HTTP status 200 and the correct JSON content.
- For the sake of simplicity we omit authorization checks at the current stage.
Run the test.
- Since the test is run with
Your integration-test module now contains a mock server test:
Step 7: Obtain credentials from SCP Neo
In case you don’t already have credentials to access a monitoring API with, you can simply create them yourself on SCP Neo.
- Use the SAP Cloud Platform Cockpit, to go to your account. In the left navigation, click on
Services
. In category DevOps make sure Monitoring is active. - In the navigation, open
Security > OAuth
. In the content frame, find the Token Endpoint, note it down – you will need this in the next step. - Next, change to the tab labeled
Platform API
. - Click the Button Create API Client. Enter a description and select the Monitoring Service checkbox.
- Note down the Client ID and Client Secret. You will need them during the destination configuration.
Step 8: Prepare configuration on SCP Cloud Foundry
Open the SCP Cockpit and navigate to your Cloud Foundry account.
- Ensure service bindings
Open the current account space, which your application is going to be uploaded to. Here, in the navigation on the left, under Services, click on Service Instances. Make sure you have an instance running for both services,xsuaa
anddestination
. For the sake of this guide, let’s assume thexsuaa
service instance is called"myxsuaa"
and thedestination
service instance is called"mydestination"
.In case you are missing a service instance, go to Service Marketplace and setup it up. Forxsuaa
, the recommended service plan is application. Fordestination
it is lite. - Add the destination
Leave the space, back to your Cloud Foundry account. In the navigation, under Connectivity click Destination, Click the button New Destination. Enter the Monitoring service destination values:- Name:
MonitoringEndpoint
Just like described in your Java application, as fieldMonitoringDestination.DESTINATION_NAME
- Type:
HTTP
- URL:
https://api.[domain].ondemand.com/monitoring/v2/
Enter the correct sub domain, depending on your landscape. You can find a list of supported URLs on the Service API page. - Proxy Type:
Internet
- Authentication:
OAuth2ClientCredentials
- Client ID + Token Service User: (Client ID from the previous step)
- Client Secret + Token Service Password: (Client Secret from previous step)
- Token Service URL: (Token Endpoint from previous step)
Note: While Monitoring v2 has only one pair of credentials, other services may distinguish between Token Service User and Client ID.
- Name:
You are done with the setup on SCP Cloud Foundry.
Step 9: Deploy application
- Run a complete Maven build with your project:
mvn clean install
All tests will be executed to make sure your application is working as expected.
- Open the
manifest.yml
Change the YAML such that XSUAA and destination services instance are bound upon application initialization. Add the following entries to the list of services:services: - myxsuaa - mydestination
Note: This may differ from your file, in case the service instances were named differently.
- In the command prompt run the following statement with the Cloud Foundry commandline interface: For information on how to use the
cf
tool, please find the starter tutorial for applications on Cloud Foundry with the SAP Cloud SDK.cf push
Note down the logged entry in
urls
above.
Step 10: Test
- Open the application URL. You will be greeted with the default landing page.
Enter the following path.
/monitor?account=D123456&app=sampleapp
Replace the wildcards for the request parameters. For
account
enter the (sub) account Id for which the monitored application is running. Enter the application name forapp
.You should see the successful response from your application.
Congratulation!
You successfully consumed a service API from SCP Neo with an application on SCP Cloud Foundry, with the help of code generation and SAP Cloud SDK.
while executing mvn (Step 2: Create a new Spring application) I got: Archetype com.sap.cloud.s4hana.archetypes:scp-cf-spring:LATEST is not configured
Hi Micha,
If “LATEST” is not working as a keyword, please consider using the latest version
In case this still does not work, try the “-U” modifier in the command, i.e.
Best regards
Alexander
Further investigations led to a broken ~/.m2/settings.xml (maven configuration file). By exchanging it with a default template, we fixed the issue.
Hi Alexander,
Excellent Blog. Does this also handles oAuth Handling automatically from destination or we need to do that. I am working on connecting to API's on NEO from Cloud Foudry. I have created Token Endopoint in NEO. I have created destination for the API's with Client id and Secret with Token Endpoint in CF along with SCOPE.
Can you please share something on that.
Regards,
Rajiv
Hi Rajiv,
The OAuth token handling works automatically. Internally the destination service of CF is negotiating the token. If you followed the steps described in this post for ClientCredentials based authentication, no further actions are required.
Not sure about the context of "scope" you mentioned though.
Best regards
Alexander
Hi Alexander,
Thanks for shairing this nice blog.I followed everything but after running command mvn clean install i am getting below error.
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project org.example.rest-application: Compilation failure
[ERROR] /D:/org.example.rest/application/src/main/java/org/example/rest/controllers/MonitorController.java:[29,98] cannot find symbol
[ERROR] symbol: method getAccountsSubaccountNameAppsAppNameMetrics(java.lang.String,java.lang.String)
[ERROR] location: class org.example.rest.monitoring.api.JavaApplicationMetricsApi
Can you help me to sort out the issue or is any way to get the org.example.rest-application.jar file so i can try to push the application in my cloud foundry account and test.
Thanks,
Arpit Gupta
Software Consultant
Hi Arpit,
please be aware that this blog post targets older versions of the SAP Cloud SDK. From the error message I assume that in the mean time the API on the API Hub may have changed and therefore also the API classes that you generate here. That means that the usage of these needs to also change and the code snippets in this post are not up to date anymore. Since I don't have the generated code at hand I can't tell you if that is the case and to what extend changes are need to be made to the code.
I recommend you use your favourite IDE to discover what methods api exposes and look for what is similar to the getAccountsSubaccountNameAppsAppNameMetrics that we used to call. Maybe only parameters have changed.
Then disable the tests for now and try to run it again. In order to adapt the tests please look up the method you call in the API hub and adapt the tests to meet the current specification.
Best regards,
Matthias
Thanks Matthias 🙂
Hello Alexander,
in our project, we want to build an application on SCP Neo that consumes services from different sources.
I successfully created an app and deployed it on Neo accordingly to the following tutorial:
https://developers.sap.com/group.s4sdk-neo.html
The app was created with the following command:
mvn archetype:generate -DarchetypeGroupId=com.sap.cloud.sdk.archetypes -DarchetypeArtifactId=scp-neo-javaee7 -DarchetypeVersion=RELEASE
I was able to work with OData services using for example the DestinationAccessor and the ODataQueryBuilder.
Now, I have one remaining issue: not all of the services that we want to use are OData, some of them are REST services. For example AIN (Asset Intelligence Network) which is also available in the API business hub:
https://api.sap.com/api/EquipmentAPI/overview
Could you please let me know if I can somehow use the approach described here to achieve what we need?
As far as I could see this tutorial is intended for Cloud Foundry. I successfully performed this tutorial, but I was not able to deploy it to Neo. Probably I would need to make some changes to the Pom.xml, in order to get a WAR file that I could deploy on Neo. But I am no expert in that area.
Thanks and best regards,
Tobias
Hello Tobias,
You are right, this tutorial is about consuming REST APIs with Cloud Foundry Apps for SAP Cloud SDK ver. 2
I'll take a look at this issue and discuss possible solutions with Alex. Can you please clarify the following:
Thanks for patience and hope for a quick follow up from you. In the meanwhile I'll investigate this in a bit more details.
Best,
Artem
Hello Artem,
thanks so much already for your response and your investigations.
Here are my answers:
Best regards,
Tobias
Hi Tobias, thanks a lot for clarifications. It brings a lot of clarity.
My immediate hunch here would be:
I’ll discuss these steps with the team shortly to see what particularities are there to consider so that everything runs smoothly for you.
Can you please give me additional details about what exactly fails when you deploy to Neo? Error log? Stack trace? Please, remove any sensitive data if any before posting it here.
It would be also nice if you can host your tutorial’s code somewhere on GitHub and share a link with me.
The blog article I was referring to is this one. You should be aware of it in case you already use SDK ver. 3.
I would also encourage you to look at this page for SDK docs and Javadoc
Regarding Cloud Foundry (CF) vs Neo I’d recommend looking at this comparison.
General consideration would be to start new development with Cloud Foundry as more developed and feature reaches the environment unless you have restriction binding to use Neo.
I hope it helps and waiting for your follow up.
------------- UPDATE 1:
I discussed with the team and confirmed that all the code related to REST consumption should be compliant with SDK v.3 and cross-platform. Meaning it should work both on Neo and CF.
Please, make sure that you bootstrap an App with Neo archetype because you won't be able to deploy CF build to Neo platform. But the REST part should be fully reusable.
We are happy to help further if you see any errors while following the mentioned steps.
---------------------------
Best,
Artem
Hi Artem,
thanks so much for your hints, they are very helpful.
What I now did is to try to incorporate the code that was created for my API (https://api.sap.com/api/EquipmentAPI/overview) into my Neo project (DarchetypeArtifactId=scp-neo-javaee7).
I now have a war file that I can successfully deploy to Neo and start. The OData calls are still working fine. For rest APIs I get error 403. Even though I think it is called using the destination, there seems to be some kind of authorization issue?
Here is the error that I receive:
Here is the code for calling Odata which is working fine:
And the code for the Rest call where I get the error:
So in both cases the same destination (AIN) is used. It is of type OAuth2ClientCredentials, and in general it works.
It would be great if you could let me know if how I tried to do it is the correct approach. Maybe you also have an idea how to fix the issue.
Thanks and best regards,
Tobias
Hi, Tobias, I'm happy we're making some progress.
I'll take a look at what might be wrong with auth here. Let me a bit of time to get a better idea about what's going on here and talk to my colleagues:)
You can continue hacking in the meanwhile and please keep me posted if you have any additional findings!
A small request from me to post an exception stack trace as text because somehow the image is not scalable in the comments. I saved it and could read it, but the text would be still faster and easier to handle.
I'll get back to you as soon as I have ideas about solving this!
Best,
Artem
Hey Tobias, I’m back with some suggestions and comments
The good thing is that as far as I can see your code looks correct. It’s also confirmed by the fact that your request gets through and you see a 403 (Forbidden) in response.
From here we can do the following:
Please, get back to me after you have some results from my suggestions.
I hope it helps!
Best,
Artem
To see the images in better resolution, please save them to your PC then you'll be able to see them full size.
Hi Artem,
I activated some logs in the Neo Cockpit for my application and here is what I got:
URL (seems correct to me):
HTTP GET https://ain-live.cfapps.eu10.hana.ondemand.com/equipment(<EquipmentId>)/values?status=2
There is also an entry for the Authentification: Bearer <token>, however I have no idea where it is coming from:
My destination seems to be ok:
By the way, we are using a destination of type OAuth2ClientCredentials that was created according to the following link:
https://help.sap.com/viewer/d89bac86aa294f75afdc40dca457dd7b/1911/en-US/b2f6f1acc25744a29c177f25d9bb3bce.html
In general we can work successfully with this destination.
In the cockpit it looks like this:
I will try to make some tests with Postman as you suggested.
Any further hint would be very much appriciated!
Thanks and best regards,
Tobias
Hi Artem,
I made some tests with Postman and was able to retrieve data successfully.
I did it in the following way.
GET request to the following URL (same as in my destination):
https://ain-live.cfapps.eu10.hana.ondemand.com/ain/services/api/v1/equipment
I used an authorization of type OAuth2.0:
I used the GetNewAccessToken button in order to get the token for my Client ID and credentials:
Best regards,
Tobias
Hi Artem,
in the meantime I am one step further:
I found out that my base path was wrong, it containted only the path to the application and not the complete API path.
I changed it to:
Now I can call it successfully (HTTP Status 200).
I am getting the following from the apiInstance.equipmentEquipmentIdValuesGet function:
I was expecting (hoping) I would get the same result as when I call the PAI e.g. in Postman:
Do you have any idea what I have to change in order to get this?
Thanks and best regards,
Tobias
Hi Tobias,
Thanks for the extensive findings. I'll check them out and get back to you ASAP. It seems like we're getting closer if you can successfully query the API.
Hi Artem,
in the meantime I figured out how to work with the objects that I get back from the API call.
So everything is fine (for now 🙂
Thanks for all your support and best regards,
Tobias
Hi Tobias,
Sorry for getting this long to get back. I had too much in my pipeline.
I'd appreciate it if you share your findings with me.
We'll be updating this blog article and converting it into a tutorial. Your feedback and findings will help us a lot to make it better and easy to follow.
I already see your discovery about the base path, what was it with processing the returned data that gave you empty results at the end? How did you solve it?
Thanks in advance for sharing and have a great weekend!