Technical Articles
Migration Guide: Moving to Version 2.0.0 of the SAP S/4HANA 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.
With the release of version 2.0.0 of the SAP S/4HANA Cloud SDK, along with dropping support for Java 7, we were able to incorporate several improvements and add some great new features to the Java libraries of the SDK based on your feedback! Along with the modernization of the technology stack, we took the chance to clean up our code base and remove obsolete and unwanted functionality that – in a few cases – required breaking changes to our API.
In order to provide you a smooth transition to our latest release, this migration guide aims to help you in moving your project to version 2.0.0 of the SAP S/4HANA Cloud SDK. In particular, this blog post will outline the relevant changes in detail and offer code examples that demonstrate the steps that are necessary for a successful and seamless migration.
For a general overview of the latest changes, please refer to our release notes.
Leverage Java 8
Move from Guava’s to Java’s Functional Primitives
In addition Replace Guava’s functional primitives like FluentIterable
, Optional
, Function
, Predicate
and Supplier
with corresponding native Java 8 types. This usually means that you have to adjust the existing Guava imports and replace them with their Java 8 counterparts. For instance, differences between Guava’s and Java’s Optional are described in the Guava Javadoc. An example for the required changes is depicted below.
Previously
import com.google.common.base.Optional;
...
ProxyConfiguration proxyConfiguration =
DestinationAccessor.getDestination("dest")
.getProxyConfiguration()
.orNull();
Now
import java.util.Optional;
...
ProxyConfiguration proxyConfiguration =
DestinationAccessor.getDestination("dest")
.getProxyConfiguration()
.orElse(null);
The interface Executable
no longer inherits from Callable
and is now annotated with @FunctionalInterface
. RequestContextExecutor
has been adjusted accordingly to accept both Callable
and Executable
as Lambda expressions. This enables a simpler use of this class:
Previously
new RequestContextExecutor().execute(new Executable() {
@Override
public void execute() throws Exception {
...
}
);
Now
new RequestContextExecutor().execute(() -> {
...
});
Move from Joda-Time to Java’s Date and Time
With version 2.0.0, the SDK replaces the use of Joda-Time as well as the legacy Java 7 date and time classes with the corresponding java.time
classes of Java 8. In particular, in the Java virtual data model (VDM), the deprecated Calendar
type has been replaced with LocalDateTime
, LocalTime
, and ZonedDateTime
. When adapting your code, you will be able to see the new expected type on the respective entity fields.
Change Obsolete or Unwanted Behavior
CacheKey
The potentially risky best-effort cache keys and HystrixUtil
command key methods have been removed. The CachingCommand
now uses CacheKey.ofTenantAndUserIsolation()
by default. While the best-effort isolation allowed to generically construct a cache key with the highest possible level of isolation, it beared the potential risk of an unexpected cache isolation, for example due to a security configuration mistake. Therefore, we decided to require an explicit specification of the expected isolation instead. If the requested isolation level cannot be provided, an exception is thrown instead of silently reverting to a weaker isolation.
Previously
// weaker than expected isolation possible at runtime
CacheKey cacheKey = CacheKey.newBestEffortIsolatedKey();
Now
// explicitly specify and enforce isolation at runtime
CacheKey cacheKey = CacheKey.ofTenantAndUserIsolation();
Note: As a consequence of removing the best-effort cache key, a tenant is already required when using the destination service on Cloud Foundry. If you are in an environment without security being configured, set the ALLOW_MOCKED_AUTH_HEADER
environment variable to true
to mock a tenant and still use the destination service (without principal propagation).
MockUtil
In previous versions, the SDK used to implicitly mock an audit log with calling MockUtil.mockDefaults()
. Since this represented a deviation of behavior between tests, a local container, and code that runs on SAP Cloud Platform, the audit log is no longer mocked by this method. In contrast, if you need to mock an audit log, you now have to explicitly call MockUtil.mockAuditLog()
.
Previously
private static final MockUtil mockUtil = new MockUtil();
@Test
public void test()
{
mockUtil.mockDefaults(); // implicitly mocked an audit log
}
Now
private static final MockUtil mockUtil = new MockUtil();
@Test
public void test()
{
mockUtil.mockDefaults(); // no longer mocks an audit log
mockUtil.mockAuditLog(); // mock the audit log explicitly now
}
Clean up API of SAP S/4HANA Cloud SDK
Cloud Platform Abstractions
Within version 2.0.0, the APIs of the Cloud Platform abstractions have been improved with accessor methods returning Optional
s in TenantAccessor
, UserAccessor
, and SecretStoreAccessor
. For example, in order to retrieve the current tenant, previous versions only offered a method getCurrentTenant()
that could throw a TenantNotAvailableException
. Since the flow of an application can depend on the availability of a tenant, the previous APIs required developers to rely on the anti-pattern of using exceptions for controlling the code flow. Therefore, the SDK now offers new methods such as getCurrentTenantIfAvailable()
which avoid the use of an exception here.
Previously
Tenant getCurrentTenant()
throws TenantNotAvailableException,
TenantAccessException;
Now
Optional<Tenant> getCurrentTenantIfAvailable()
throws TenantAccessException;
Tenant getCurrentTenant()
throws TenantNotAvailableException,
TenantAccessException;
ErpConfigContext and ErpEndpoint
For historic reasons, previous versions of the SDK offered the class ErpEndpoint
as well ErpConfigContext
. Since both classes eventually turned out to be very similar, the purpose and use of both classes became confusing for developers. In particular, since an ErpEndpoint
always had to be constructed from a given ErpConfigContext
, developers often had to wrap an ErpConfigContext
into an ErpEndpoint
without an obvious reason. Therefore, we decided to only offer ErpConfigContext
to represent the context for transparently connecting to SAP S/4HANA systems via different protocols.
Previously
new DefaultBusinessPartnerService()
.getAllBusinessPartner()
.execute(new ErpEndpoint(new ErpConfigContext()));
Now
new DefaultBusinessPartnerService()
.getAllBusinessPartner()
.execute(new ErpConfigContext());
RequestContext Handling
In previous versions of the SAP S/4HANA Cloud SDK, SDK-internal RequestContextListener
s were initialized using ServletContextListener
s. The downside of this was that RequestContextListener
s would not be initialized when being used within certain test setups or when using another ServletContextListener
. Therefore, the SDK-internal RequestContextListener
instances are now initialized using the ServiceLoader
mechanism, allowing developers to more easily use SDK components within tests and during application startup.
Previously
@WebListener
public class MyServletContextListener implements ServletContextListener
{
@Override
public void contextInitialized( ServletContextEvent servletContextEvent )
{
// explicitly register RequestContextListeners
new RequestContextExecutor().withListeners(
new DestinationsRequestContextListener(),
new ScpNeoDestinationsRequestContextListener(),
new TenantRequestContextListener(),
new UserRequestContextListener()
).execute(...);
}
...
}
Now
@WebListener
public class MyServletContextListener implements ServletContextListener
{
@Override
public void contextInitialized( ServletContextEvent servletContextEvent )
{
new RequestContextExecutor().execute(...);
}
...
}
Furthermore, the method requestContextExecutor()
has been removed from the class MockUtil
. It is now possible to simply use new RequestContextExecutor()
instead.
Previously
private static final MockUtil mockUtil = new MockUtil();
@Test
public void test()
{
mockUtil.requestContextExecutor().execute(...);
}
Now
@Test
public void test()
{
new RequestContextExecutor().execute(...);
}
RequestContext
now uses a custom class Property
to represent a property with a certain value or exception if the value could not be determined.
Previously
Optional<Object> getProperty( String name )
throws RequestContextPropertyException;
Now
Optional<Property<?>> getProperty( String name )
throws RequestContextPropertyException;
Boostrap Hystrix on SAP Cloud Platform Neo
Previous versions of the SDK relied on a ServletContextListener
to register a Neo-specific concurrency strategy for Hystrix in ScpNeoHystrixBootstrapListener
. This is now obsolete since the SDK internally uses the standard Java ServiceLoader
mechanism offered by Hystrix to register this strategy.
Previously
@WebListener
public class MyServletContextListener implements ServletContextListener
{
@Override
public void contextInitialized( ServletContextEvent servletContextEvent )
{
// ensure correct use of HystrixConcurrencyStrategy on Neo
new ScpNeoHystrixBootstrapListener().bootstrap();
...
}
...
}
Now
@WebListener
public class MyServletContextListener implements ServletContextListener
{
@Override
public void contextInitialized( ServletContextEvent servletContextEvent )
{
// nothing special required here anymore
...
}
...
}
Apply Consistent Naming
Constructor Methods
In order to align the naming of constructor methods in the the SAP S/4HANA Cloud SDK with common terminology of Java 8, we simplified CacheKey
with unified of
constructor methods.
Previously
CacheKey.newGlobalKey();
CacheKey.newTenantIsolatedKey();
CacheKey.newUserIsolatedKey();
CacheKey.newUserOrTenantIsolatedKey();
CacheKey.newTenantIsolatedOrGlobalKey();
CacheKey.newBestEffortIsolatedKey();
// and many more
Now
CacheKey.ofNoIsolation();
CacheKey.ofTenantIsolation();
CacheKey.ofTenantAndUserIsolation();
CacheKey.of(nullableTenantId, nullableUserName);
Furthermore, unified namings have been applied to ConvertedObject
with of
constructor methods and fixing the misspelled term “convertable”, replacing it with the corrected term “convertible”.
Previously
ConvertedObject.fromConverted(nullableValue);
ConvertedObject.fromNull();
ConvertedObject.notConvertable();
Now
ConvertedObject.of(nullableValue);
ConvertedObject.ofNull();
ConvertedObject.ofNotConvertible();
Virtual Data Model
The methods for retrieving the value of association properties are no longer named getXOrNull
. Instead, these methods are now called getXIfPresent
to better reflect the return type Optional
.
Previously
Optional<Customer> customer =
businessPartner.getCustomerOrNull(); // returns Optional, not null
Now
Optional<Customer> customer = businessPartner.getCustomerIfPresent();
Improvements
Mocking of Tenant and User
The SDK now offers the environment variables USE_MOCKED_TENANT
and USE_MOCKED_USER
for fine-granular control over mocking of the current tenant and user at runtime.
- When these environment variables are set to
true
, the application will use a mocked tenant (with an empty tenant identifier""
) and/or mocked user (with an empty user name""
) instead of an actual tenant or user, respectively. - This should not be used in production, but only for testing purposes. As a consequence, security errors are logged when you use these environment variables.
- The behavior is similar to the
ALLOW_MOCKED_AUTH_HEADER
environment variable on Cloud Foundry, but in contrast to this variable it works on both Neo and Cloud Foundry and takes precedence over an actual tenant and user.
Mocking of Cloud Platform Abstractions
MockUtil
now uses the AuditLog
, CloudPlatform
, GenericDestination
, Destination
, RfcDestination
, Tenant
, User
, and SecretStore
type of the respective Cloud platform for mocking based on the chosen dependency of Cloud Foundry or Neo. This makes it easier to run the same code within tests, a local container, and on SAP Cloud Platform and allows to more easily test application logic that relies on the environment-specific implementation of a platform abstraction.
Previously
private static final MockUtil mockUtil = new MockUtil();
@Test
public void test()
{
final Tenant currentTenant = mockUtil.mockCurrentTenant();
// currentTenant was mocked as an instance of Tenant
assertTrue( currentTenant instanceof ScpCfTenant ); // would fail
}
Now
private static final MockUtil mockUtil = new MockUtil();
@Test
public void test()
{
final Tenant currentTenant = mockUtil.mockCurrentTenant();
// currentTenant is now mocked as an instance of ScpCfTenant
assertTrue( currentTenant instanceof ScpCfTenant ); // works!
}
Conclusion
This concludes our migration guide for moving to version 2.0.0 of the SAP S/4HANA Cloud SDK. We will update the content of this blog post based on your feedback on migration challenges that we may have overlooked so far. Let us know if you run into issues, for example by commenting on this blog post or posting your question on StackOverflow using the s4sdk
tag!