3.60.0
to be precise) to the latest one. The demo application has initially been created using the scp-cf-spring
SAP Cloud SDK archetype. It's main purpose is to interact with the SAP Business Partner service.BusinessPartnerContrtoller.java
@RestController
public class BusinessPartnerController {
private final BusinessPartnerService service = new DefaultBusinessPartnerService();
@GetMapping("/bupa/addresses")
public List<BusinessPartnerAddress> getBusinessPartnerAddresses(@RequestParam String destinationName,
@RequestParam UUID partnerId) {
HttpDestination destination = DestinationAccessor.getDestination(destinationName).asHttp();
List<BusinessPartner> matchingPartners = fetchPartnersWithId(partnerId, destination);
if (matchingPartners.isEmpty()) {
return Collections.emptyList();
}
if (matchingPartners.size() > 1) {
throw new IllegalStateException("More than one business partner found.");
}
try {
return matchingPartners.get(0).getBusinessPartnerAddressOrFetch();
} catch (ODataException e) {
throw new IllegalStateException("Unable to fetch business partner addresses.", e);
}
}
@GetMapping("/bupa/speaksMyLanguage")
public boolean getBusinessPartnerSpeaksMyLanguage(@RequestParam String destinationName,
@RequestParam UUID partnerId) {
HttpDestination destination = DestinationAccessor.getDestination(destinationName).asHttp();
List<BusinessPartner> matchingPartners = fetchPartnersWithId(partnerId, destination);
if (matchingPartners.isEmpty()) {
return false;
}
if (matchingPartners.size() > 1) {
throw new IllegalStateException("More than one business partner found.");
}
return businessPartnerSpeaksMyLanguage(matchingPartners.get(0));
}
private List<BusinessPartner> fetchPartnersWithId(UUID partnerId, HttpDestination destination) {
try {
return service
.getAllBusinessPartner()
.filter(BusinessPartner.BUSINESS_PARTNER_UUID.eq(partnerId))
.execute(destination);
} catch (ODataException e) {
throw new IllegalStateException("Unable to fetch business partners.", e);
}
}
private boolean businessPartnerSpeaksMyLanguage(BusinessPartner partner) {
String correspondenceLanguage = partner.getCorrespondenceLanguage();
return RequestAccessor
.tryGetCurrentRequest()
.map(request -> request.getHeaders("Accept-Language"))
.map(values -> (List) Collections.list(values))
.filter(values -> !values.isEmpty())
.getOrElse(Collections.singletonList("en"))
.stream()
.anyMatch(language -> language.equals("*")
|| language.substring(0, 2).equalsIgnoreCase(correspondenceLanguage));
}
}
.execute(HttpDestinationProperties)
API in our fetchPartnersWithId
method. Therefore, we have to catch and handle the com.sap.cloud.sdk.odatav2.connectivity.ODataException
since it's a checked exception.getBusinessPartnerAddressOrFetch()
API to avoid eagerly fetching all addresses when requesting our Business Partners. Inconveniently, this method might also throw an com.sap.cloud.sdk.odatav2.connectivity.ODataException
, which needs to be handled once again.RequestAccessor.tryGetCurrentRequest()
API (see correspondenceLanguageEqualsAcceptLanguage
method).BusinessPartnerControllerTest.java
RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class BusinessPartnerControllerTest {
private static final String DESTINATION_NAME = "Destination";
private static final UUID BUSINESS_PARTNER_ID = UUID.fromString("00163e2c-7b39-1ed9-91d0-1182c32dc6ff");
private static final MockUtil mockUtil = new MockUtil();
@Rule
public final WireMockRule BACKEND_SYSTEM = new WireMockRule(wireMockConfig().dynamicPort());
@Autowired
private MockMvc mvc;
@Test
public void testGetBusinessPartnerAddresses() throws Exception {
mockDestination();
mockMetadataLookUp();
mockBusinessPartnerLookUp();
mockAddressesLookUp();
mvc.perform(MockMvcRequestBuilders.get("/bupa/addresses")
.queryParam("destinationName", DESTINATION_NAME)
.queryParam("partnerId", BUSINESS_PARTNER_ID.toString()))
.andExpect(status().isOk());
}
@Test
public void testGetBusinessPartnerSpeaksMyLanguage() throws Exception {
mockDestination();
mockMetadataLookUp();
mockBusinessPartnerLookUp();
mvc.perform(MockMvcRequestBuilders.get("/bupa/speaksMyLanguage")
.queryParam("destinationName", DESTINATION_NAME)
.queryParam("partnerId", BUSINESS_PARTNER_ID.toString())
.header("Accept-Language", "de-DE")).andExpect(status().isOk());
}
private void mockDestination() {
mockUtil.mockDestination(DESTINATION_NAME, URI.create(BACKEND_SYSTEM.baseUrl()));
}
private void mockMetadataLookUp() throws IOException {
String metadata = readResourceFile("service.edmx");
BACKEND_SYSTEM
.stubFor(get(urlMatching("/.*API_BUSINESS_PARTNER/\\$metadata"))
.willReturn(aResponse().withBody(metadata)));
}
private void mockBusinessPartnerLookUp() throws IOException {
String singleBusinessPartner = readResourceFile("single-business-partner.json");
BACKEND_SYSTEM
.stubFor(get(urlMatching("/.*A_BusinessPartner\\?\\$filter.*"))
.willReturn(aResponse().withBody(singleBusinessPartner)));
}
private void mockAddressesLookUp() throws IOException {
String businessPartnerAddresses = readResourceFile("business-partner-address.json");
BACKEND_SYSTEM
.stubFor(get(urlMatching("/.*to_BusinessPartnerAddress.*"))
.willReturn(aResponse().withBody(businessPartnerAddresses)));
}
private static String readResourceFile(String fileName) throws IOException {
return Resources.toString(
Resources.getResource("BusinessPartnerControllerTest/" + fileName),
StandardCharsets.UTF_8);
}
}
200
code.MockUtil
class to mock a Destination
that points to our Wiremock backend service./$metadata
endpoint and serves a correct .edmx
specification (done in the mockMetadataLookUp
method).<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sap.cloud.sdk</groupId>
<artifactId>sdk-bom</artifactId>
- <version>3.60.0</version>
+ <version>3.75.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] migration - Root [pom]
[INFO] migration - Application [jar]
[INFO] migration - Integration Tests [jar]
...
[WARNING] /application/src/main/java/com/sap/example/controllers/BusinessPartnerController.java: /application/src/main/java/com/sap/example/controllers/BusinessPartnerController.java uses or overrides a deprecated API.
[WARNING] /application/src/main/java/com/sap/example/controllers/BusinessPartnerController.java: Recompile with -Xlint:deprecation for details.
...
[WARNING] /integration-tests/src/test/java/com/sap/example/BusinessPartnerControllerTest.java: /integration-tests/src/test/java/com/sap/example/BusinessPartnerControllerTest.java uses or overrides a deprecated API.
[WARNING] /integration-tests/src/test/java/com/sap/example/BusinessPartnerControllerTest.java: Recompile with -Xlint:deprecation for details.
BusinessPartnerController#fetchPartnersWithId
: The .execute(HttpDestinationProperties)
API is deprecatedBusinessPartnerContoller#correspondenceLanguageEqualsAcceptLanguage
: The RequestAccessor
class is deprecated.BusinessPartnerControllerTest
: The MockUtil
class is deprecatedMockUtil
MockUtil
class. As you have seen, it is used only to mock a Destination
. Luckily, the same behavior can be achieved easily with productive SAP Cloud SDK APIs.Destination
public class BusinessPartnerControllerTest {
private static final String DESTINATION_NAME = "Destination";
private static final UUID BUSINESS_PARTNER_ID = UUID.fromString("00163e2c-7b39-1ed9-91d0-1182c32dc6ff");
- private static final MockUtil mockUtil = new MockUtil();
@Rule
public final WireMockRule BACKEND_SYSTEM = new WireMockRule(wireMockConfig().dynamicPort());
@Autowired
private MockMvc mvc;
+ @After
+ @Before
+ public void resetDestinationAccessor() {
+ DestinationAccessor.setLoader(null);
+ }
...
private void mockDestination() {
- mockUtil.mockDestination(DESTINATION_NAME, URI.create(BACKEND_SYSTEM.baseUrl()));
+ DefaultDestinationLoader destinationLoader = new DefaultDestinationLoader().registerDestination(
+ DefaultHttpDestination.builder(BACKEND_SYSTEM.baseUrl()).name(DESTINATION_NAME).build());
+
+ DestinationAccessor.setLoader(destinationLoader);
}
MockUtil
class entirely removed from our tests, we can also get rid of the corresponding dependency.Integration-Tests/pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
-<dependency>
- <groupId>com.sap.cloud.sdk.testutil</groupId>
- <artifactId>testutil-core</artifactId>
- <scope>test</scope>
-</dependency>
.execute
.execute(HttpDestinationProperties)
API is almost effortless. In our demonstration, we just have to exchange the call with the stable .executeRequest(HttpDestinationProperties)
API.fetchPartnersWithId
With .executeRequest
private List fetchPartnersWithId(UUID partnerId, HttpDestination destination) {
try {
return service
.getAllBusinessPartner()
.filter(BusinessPartner.BUSINESS_PARTNER_UUID.eq(partnerId))
- .execute(destination);
+ .executeRequest(destination);
} catch (ODataException e) {
throw new IllegalStateException("Unable to fetch business partners.", e);
}
}
.executeRequest
API, you will notice your IDE complaining about the catch (final ODataException e)
clause. This is because the .executeRequest
method does not throw an com.sap.cloud.sdk.odatav2.connectivity.ODataException
.try ... catch ...
block, so that the method afterwards looks as below:fetchPartnersWithId
Implementationprivate List fetchPartnersWithId(UUID partnerId, HttpDestination destination) {
return service
.getAllBusinessPartner()
.filter(BusinessPartner.BUSINESS_PARTNER_UUID.eq(partnerId))
.executeRequest(destination);
}
RequestAccessor
RequestAccessor
provides convenient access to the ServletRequest
sent by the user to our application. Unfortunately, the ServletRequest
is very limited when it comes to performing (long-running) asynchronous operations as part of the request processing. Therefore, we introduced a new accessor for getting the most prominent part of the ServletRequest
: The HTTP headers.RequestAccessor
with the new RequestHeaderAccessor
like so:correspondenceLanguageEqualsAcceptLanguage
With RequestHeaderAccessor
private boolean businessPartnerSpeaksMyLanguage(BusinessPartner partner) {
String correspondenceLanguage = partner.getCorrespondenceLanguage();
- return RequestAccessor
- .tryGetCurrentRequest()
- .map(request -> request.getHeaders("Accept-Language"))
- .map(values -> (List) Collections.list(values))
+ return RequestHeaderAccessor
+ .tryGetHeaderContainer()
+ .map(headers -> headers.getHeaderValues("Accept-Language"))
.filter(values -> !values.isEmpty())
.getOrElse(Collections.singletonList("en"))
.stream()
.anyMatch(language -> language.equals("*")
|| language.substring(0, 2).equalsIgnoreCase(correspondenceLanguage));
}
<dependencyManagement>>
<dependencies>
<dependency>
<groupId>com.sap.cloud.sdk</groupId>
<artifactId>sdk-bom</artifactId>
- <version>3.75.0</version>
+ <version>4.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
$ mvn clean verify
...
[ERROR] 'dependencies.dependency.version' for com.sap.hcp.cf.logging:cf-java-logging-support-logback:jar is missing.
...
[ERROR] /application/src/main/java/com/sap/example/controllers/BusinessPartnerController.java:[17,46] package com.sap.cloud.sdk.odatav2.connectivity does not exist
[ERROR] /application/src/main/java/com/sap/example/controllers/BusinessPartnerController.java:[54,24] cannot find symbol
[ERROR] symbol: class ODataException
sdk-bom
. This means that we have reduced the managed dependencies down to those that are actually used in our "core" modules. Therefore, if your application is relying on dependency versions that were previously managed by the sdk-bom
, you need to check whether that is still the case.version
tag to it:<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-logback</artifactId>
+ <version>3.6.3</version>
</dependency>
com.sap.hcp.cf.logging:cf-java-logging-support-logback
will reveal that the latest version is 3.6.3
..execute(HttpDestinationProperties)
API. Apparently, this was not enough as indicated by the second compiler issue from above.import
statement in our BusinessPartnerContoller
class:import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
import com.sap.cloud.sdk.cloudplatform.requestheader.RequestHeaderAccessor;
-import com.sap.cloud.sdk.odatav2.connectivity.ODataException;
+import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException;
getBusinessPartnerAddressOrFetch()
method (used in the addresses
endpoint).get...OrFetch
APIs received an under-the-hood improvement to no longer throw the checked exception we were catching previously. Instead, now they might throw an com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException
, which is a runtime exception. Therefore, we are free to apply the same change as for the .execute
replacement: We can remove the try ... catch ...
block and our application will still compile.version
tags to dependencies that are no longer managed within the sdk-bom
.ODataException
.You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
19 | |
10 | |
7 | |
7 | |
7 | |
6 | |
6 | |
6 | |
5 | |
5 |