
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.
cf version
without an error.mvn archetype:generate -DarchetypeGroupId=com.sap.cloud.s4hana.archetypes -DarchetypeArtifactId=scp-cf-spring -DarchetypeVersion=RELEASE
RELEASE
reference at the end of the command to your preferred version. This tutorial uses org.example.rest
as base package../application/src/main/resources/api.yaml
./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>
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>
/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 the api-options.json
with the following content, and put it next to api.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"
}
"library" : "resttemplate"
intact, to ensure support with SAP Cloud SDK.mvn generate-sources
target
directory of the application
module:./application/target/generated-sources/openapi/src/main/java/
operationId
elements in the api.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.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 the model
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.MonitoringDestination.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);
}
}
ConfigurationMonitoring.java
to serve beans with request scope, to ensure tenant and user separation. With the MonitoringDestination
reference we can resolve service paths and the HttpClient
, 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;
}
}
@Primary
we signal Spring to select this instantiation method with priority over the methods provided by default from the generated OpenAPI code.models/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;
}
}
controllers/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 class MonitorResponse
. You can later manipulate the result
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));
}
}
GET /monitor
request. Upon execution an instance of ApiClient
is injected as part of the class constructor. Spring will resolve the bean from our previously introduced ConfigurationMonitoring
configuration.integration-tests
module../integration-tests/src/test/resources/mocked_monitoring_response.json
[
{
"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
}
]
}
]
}
]
./integration-tests/src/test/java/[...]/MonitoringControllerLocalTest.java
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+"}"));
});
}
}
.andExpect(...)
statements to improve test assertions.SpringRunner
and annotated with @WebMvcTest
we can use the autowired MockMvc
instance to directly call our controller.GET /monitoring/v2/accounts/d012345trial/apps/sample-application/metrics
Services
. In category DevOps make sure Monitoring is active.Security > OAuth
. In the content frame, find the Token Endpoint, note it down - you will need this in the next step.Platform API
.xsuaa
and destination
. For the sake of this guide, let's assume the xsuaa
service instance is called "myxsuaa"
and the destination
service instance is called "mydestination"
.In case you are missing a service instance, go to Service Marketplace and setup it up. For xsuaa
, the recommended service plan is application. For destination
it is lite.MonitoringEndpoint
Just like described in your Java application, as field MonitoringDestination.DESTINATION_NAME
HTTP
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.Internet
OAuth2ClientCredentials
mvn clean install
manifest.yml
services:
- myxsuaa
- mydestination
cf
tool, please find the starter tutorial for applications on Cloud Foundry with the SAP Cloud SDK.cf push
urls
above./monitor?account=D123456&app=sampleapp
account
enter the (sub) account Id for which the monitored application is running. Enter the application name for app
.You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
12 | |
12 | |
11 | |
11 | |
11 | |
9 | |
8 | |
7 | |
7 | |
6 |