Skip to main content

Use Destinations To Connect To Other Systems and Services

The SAP Cloud SDK offers some basic functionality that helps with connecting to other systems and services like SAP S/4HANA Cloud. The SAP Cloud SDK introduces the general concept of a Destination which holds basic information about how to connect to such a system. That could for instance be a url, a username, and password for basic authentication or some custom headers.

This concept is integrated with the Destination Service that is available on the SAP Business Technology Platform. If the application has a service binding to this service in place the SAP Cloud SDK will provide access to these destinations.

Accessing Destinations#

In general destinations are accessed through the DestinationAccessor:

DestinationAccessor.getDestination("my-destination");

This will look up the destination in the destination service if the application is running on SAP Business Technology Platform. Other sources like the environment variables are also considered.

Hint

To minimize the runtime overhead of finding destinations, the SAP Cloud SDK uses caching to internally maintain a list of already retrieved destinations. Therefore, subsequent calls to DestinationAccessor.getDestination("my-destination"); will be served from that cache instead of performing the look up over and over again. Cache entries will automatically be invalidated 5 minutes after their initial creation.

Supported Authentication Flows#

We provide support for the following authentication types on SAP BTP, Cloud Foundry environment.

Authentication TypeParameter Name
NoAuthenticationAuthenticationType.NO_AUTHENTICATION
BasicAuthenticationAuthenticationType.BASIC_AUTHENTICATION
PrincipalPropogationAuthenticationType.PRINCIPAL_PROPAGATION
ClientCertificateAuthenticationAuthenticationType.CLIENT_CERTIFICATE_AUTHENTICATION
AppToAppSSOAuthenticationType.APP_TO_APP_SSO
OAuth2ClientCredentialsAuthenticationType.OAUTH2_CLIENT_CREDENTIALS
OAuth2SAMLBearerAssertionAuthenticationType.OAUTH2_SAML_BEARER_ASSERTION
OAuth2UserTokenExchangeAuthenticationType.OAUTH2_USER_TOKEN_EXCHANGE
OAuth2JWTBearerAuthenticationType.OAUTH2_JWT_BEARER
SAPAssertionSSOAuthenticationType.SAP_ASSERTION_SSO
InternalSystemAuthenticationAuthenticationType.INTERNAL_SYSTEM_AUTHENTICATION
OAuth2PasswordAuthenticationType.OAUTH2_PASSWORD
SAMLAssertionAuthenticationType.SAML_ASSERTION

Integrated Multi-Tenancy#

By default, the DestinationAccessor is tenant aware. If a tenant is available, it will be used to access the destination service on behalf of that tenant. If no destination is found in the tenant-specific destination service, the SAP Cloud SDK will try to get it using the service binding of the application.

This default retrieval strategy can be overridden by passing options to the destination loader as follows:

DestinationAccessor.getLoader().tryGetDestination(destinationName, options);

See the section on destination options below.

Decorating Destinations#

Depending on the use case, one needs to wrap the accessed destination before issuing a request to a system. This is to make sure all required destination properties are correctly set before invoking the actual request.

HTTP Destinations#

In case of HTTP connections one needs to wrap the retrieved destination as HttpDestination using asHttp():

DestinationAccessor.getDestination("my-destination").asHttp();

This method ensures that the required destination properties are all set to make the HTTP connection. With the resulting destination instance depending on the use case one can run HTTP queries for OData or REST.

BAPI Destinations#

Similarly, for BAPI endpoints you need to use asRfc():

DestinationAccessor.getDestination("my-destination").asRfc();

Connect to SAP S/4HANA On-Premise#

If your destination is exposing an SAP S/4HANA On-premise service via a Cloud Connector you need to decorate the destination with DefaultErpHttpDestination:

final HttpDestination httpDestination =
destination.asHttp().decorate(DefaultErpHttpDestination::new);
  • This ensures the mapping of all SAP S/4HANA properties like sap-client and sap-locale as HTTP request headers.
  • In case Principal Propagation is specified as an authentication type for the destination the SAP Cloud SDK will automatically apply it.

Registering Destinations at Runtime#

The SAP Cloud SDK offers convenient ways to create a destination at runtime and register it so that it will be available via DestinationAccessor.getDestination(). This is especially useful when working in a local environment. Destinations configured in the destination service are not available in a local setting and have to be mocked.

Mocking Destinations for Testing#

The SAP Cloud SDK offers a MockUtil class that is capable of injecting destinations into the DestinationAccessor:

final MockUtil mockUtil = new MockUtil();
MockDestination destination = MockDestination
.builder("my-destination", URI.create("http://localhost:8080"))
.build();
mockUtil.mockDestination(destination);
// This will now return the mocked destination
DestinationAccessor.getDestination("my-destination").asHttp();

This helps with keeping production and test code nicely separated. There are more overloads of the mocking on the MockUtil class that you can use. Refer to these tutorial steps on how to mock destinations for local development and testing.

Using Custom Destination Loaders#

The above is useful for testing but not suited for productive code. If you want to register custom destinations in your productive code you can use the following API:

customHttpDestination = DefaultHttpDestination.builder("http://url")
.name("custom-destination")
.build();
customLoader = new DefaultDestinationLoader()
.registerDestination(customHttpDestination);
DestinationAccessor.appendDestinationLoader(loader);
// This will now return the custom destination
DestinationAccessor.getDestination("custom-destination").asHttp();

By default, the DestinationAccessor is using a DestinationLoaderChain that comprises multiple loaders. These are e.g. a loader to get destinations from the destination service and a loader that reads destinations from environment variables.

The above appendDestinationLoader() will add the provided loader at the end of such a chain. That means the new loader can operate as a fallback. Use prependDestinationLoader() to add it at the beginning if you would like it to take precedence. Lastly use setLoader() to replace all existing loaders.

Provide Headers for Destinations#

How It Works#

You can add headers that are sent for any request over destinations. You can implement custom logic that defines which headers are sent under which circumstances.

Example Use-case

Let's imagine, you want to send an API token via a request header with any calls made over any destination in your whole application.

The SAP Cloud SDK offers the interface DestinationHeaderProvider which defines the method List<Header> getHeaders(DestinationRequestContext). At runtime, while preparing an outbound request, the SAP Cloud SDK will use all implementations of that interface which have been registered via the Java ServiceLoader mechanism (see below). For each registered implementation it invokes the method getHeaders(DestinationRequestContext) and appends the returned headers to the currently prepared request.

Implement Own Header Provider#

Implement the Interface DestinationHeaderProvider#

To implement your custom destination header provider, you need to implement the interface DestinationHeaderProvider. The method getHeaders provides access to the request specific context. The method argument of type DestinationRequestContext lets you access the general properties over which the currently prepared request will be issued. Depending on your logic to determine headers, you can read the following data types:

  • DestinationProperties getDestination() (e.g. the general destination name).
  • URI getRequestUri() (e.g. the request specific query parameters)

Here is one simple example implementation which returns the same header for each request:

@Nonnull
@Override
public List<Header> getHeaders( @Nonnull final DestinationRequestContext requestContext )
{
final Header header = new Header("API-Token", obtainApiToken());
return Collections.singletonList(header);
}

Make Interface Implementation Accessible#

The SAP Cloud SDK uses the Service Loader mechanism of Java to look up the DestinationHeaderProvider implementations at runtime. To let your custom implementation be found, you need to provide a configuration file as resource in the folder META-INF/services. Name this resource file as the interface com.sap.cloud.sdk.cloudplatform.connectivity.DestinationHeaderProvider and mention the fully-qualified class name of your custom implementation. You can refer to several implementations by putting their fully qualified class names on separate lines.

Place the new folder + file in your resources folder. In a Maven project the resource folder is typically src/main/resources/. For example, if your custom implementation is named ApiTokenHeaderProvider in the package com.example, the configuration should be as follows:

  • File path & name:
    src/main/resources/META-INF/services/com.sap.cloud.sdk.cloudplatform.connectivity.DestinationHeaderProvider
  • File content:
    com.example.ApiTokenHeaderProvider

Retrieving Destinations#

Get All Destinations From the Destination Service on Cloud Foundry#

To fetch all destinations from the Destination Service, you need to make a call to tryGetAllDestinations. The method queries the Destination Service API and retrieves all the destinations available at the service instance and sub-account level. In case there is a destination available on both the levels with the same name, then this method prioritizes the destination at the service instance level.

Below is the sample call to tryGetAllDestinations:

final Try<Iterable<ScpCfDestination>> destinations = destinationLoader.tryGetAllDestinations(options);

In the above call destinationLoader needs to be an instance of ScpCfDestinationLoader.

Configuring Timeout Durations When Querying the Destination Service on Cloud Foundry#

By default, the TimeLimiter resilience pattern aborts requests to the Destination Service on Cloud Foundry after 6 seconds. If your app requires more time when fetching destinations, you may create your own ScpCfDestinationLoader instance by:

// Allow requests to take up to 10 seconds instead of just 6
final DestinationLoader destinationLoader = new ScpCfDestinationLoader(Duration.ofSecond(10));

This operation overrides the default timeout duration so that you are free to choose whatever value fits your needs.

Hint

You may register your destinationLoader within the DestinationAccessor by:

DestinationAccessor.setLoader(destinationLoader);

That way, all future requests to the Destination Service will use the increased timeout duration.

Building Destination Options#

You need to build a DestinationOptions object and pass it as a parameter. It defines how Destinations Service is queried. In a simple application without provider/subscriber setup, your initial configuration is as simple as:

final DestinationOptions options = DestinationOptions.builder().build();

For a provider/subscriber setup, a retrieval strategy must be chosen according to your particular use case from:

  • ALWAYS_SUBSCRIBER (default)
  • ALWAYS_PROVIDER
  • SUBSCRIBER_THEN_PROVIDER

Here is an example for SUBSCRIBER_THEN_PROVIDER option:

final DestinationOptions options =
DestinationOptions
.builder()
.augmentBuilder(
ScpCfDestinationOptionsAugmenter.augmenter().retrievalStrategy(
ScpCfDestinationRetrievalStrategy.SUBSCRIBER_THEN_PROVIDER))
.build();

You can similarly use other retrieval options.

Configure HTTP Destinations for Local Deployment#

When testing your app on localhost instead of SAP BTP, you require some configuration steps to make the destination retrieval work.

HTTP Destinations

This section refers to the maintenance of HTTP destinations. Refer to the documentation about BAPI/RFC communication concerning the maintenance of RFC destinations for local deployment.

Works for SAP Business Application Studio too

The configuration procedures outlined here work too when you launch your app "locally" inside SAP Business Application Studio. It does not matter that the app does not run on your own machine.

To better understand the configuration steps, we'll outline shortly how destination retrieval works when running your app on SCP. Thereafter, we'll outline the configuration procedures in detail.

Background: Destination Retrieval on SAP Business Technology Platform#

By default, the SAP Cloud SDK uses a set of so-called destination loaders in a given order when searching for a destination via the DestinationAccessor API:

DestinationAccessor.tryGetDestination("MyDestination");

Environment Variable#

The first destination loader attempts to find the requested destination in the environment variable destinations. Here is an example value:

[
{
"type": "HTTP",
"name": "MyDestination",
"proxyType": "Internet",
"description": "This destination rocks!",
"authentication": "BasicAuthentication",
"url": "https://URL",
"user": "USER",
"password": "PASSWORD"
}
]
Multiple Destinations

You can configure multiple destinations with that approach. Note that the value of the environment variable is a JSON array, so you can add further entries into it.

SAP Business Technology Platform Destination Service#

If the previous lookup was not successful, the next destination loaders examine the environment variable VCAP_SERVICES to see if the SAP BTP destination service is bound. If so, the SAP Cloud SDK queries the SAP BTP destination service for the destination configuration per the requested destination name.

SAP Business Technology Platform Connectivity Service#

In case the destination is of proxy type OnPremise, the SAP Cloud SDK performs an additional query to the SAP BTP connectivity service to obtain further proxy information and an authorization token.

Configuration Procedure for Localhost#

Destinations With Proxy Type "Internet"#

Maintain the destination configuration via the environment variable destinations. See the example value above for reference.

Destinations With Proxy Type OnPremise#

We have to some perform configuration steps to tell the SAP Cloud SDK how to access the SAP BTP destination service and connectivity service.

Are your client and server located on the same Network?

If your localhost (client) resides in the same network zone as the target system (server) you can access it directly. Configuration of SAP BTP connectivity services is not needed. All you have to do is creating a destination via environment variable destinations.

Maintain VCAP_SERVICES#

How to create the VCAP_SERVICES representation depends on whether the Cloud Application Programming (CAP) Model is used.

Create the file default-env.json in your project's root and supply the VCAP_SERVICES value you copied beforehand so that the file looks similar to the following shortened example:

{
"VCAP_SERVICES": {
"connectivity": [
{
"label": "connectivity",
"plan": "lite",
"name": "my-connectivity",
"instance_name": "my-connectivity",
"credentials": {
...
}
}
],
"xsuaa": [
{
"label": "xsuaa",
"plan": "space",
"name": "my-xsuaa",
"instance_name": "my-xsuaa",
"credentials": {
...
}
}
],
"destination": [
{
"label": "destination",
"plan": "lite",
"name": "my-destination",
"instance_name": "my-destination",
"credentials": {
...
}
}
]
}
}

Furthermore, add the dependency com.sap.cds:cds-integration-cloud-sdk to your POM file:

<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-integration-cloud-sdk</artifactId>
</dependency>

Skip Destination Creation Step for Certain Authentication Types on Cloud Foundry#

For certain authentication types i.e. NoAuthentication and OAuth2ClientCredentials there is an API that eliminates the need to manually create a destination in the CF account for connecting to services.

Example:

final Map<String, String> mapping =
ScpCfServiceDestinationLoader.buildClientCredentialsMapping(
"credentials.endpoints.service_rest_url",
"credentials.uaa.clientid",
"credentials.uaa.clientsecret",
"credentials.endpoints.token_url");

The ScpCfServiceDestinationLoader.buildClientCredentialsMapping method creates a mapping for loading a destination and provides JSON paths for all required destination properties.

final HttpDestination destination =
ScpCfServiceDestinationLoader.getDestinationForService(
"label",
"serviceBindingName",
AuthenticationType.OAUTH2_CLIENT_CREDENTIALS,
mapping);

For certain services (only SAP BTP Workflow service for now), we provide further convenience that skips the need to provide the mapping explicitly. Example:

final HttpDestination destination =
ScpCfServiceDestinationLoader.getDestinationForService(
ScpCfServiceDestinationLoader.CfServices.WORKFLOW,
"my-workflow");
Enable Access to SAP Business Technology Platform Connectivity Service#

The SAP BTP connectivity service builds the connection between SAP BTP and the On-premise network. That is why it has strong built-in restrictions to allow it only to be called from within SCP. If you call the connectivity service from your local machine, you will encounter a connection timeout. We'll therefore apply port forwarding via SSH to simulate that your localhost plays the cloud app.

  • Deploy your app to the SAP BTP once.
  • Enable SSH access to your app container with the cf CLI:
cf enable-ssh app-name
cf restart app-name
  • Inspect the value of the entry connectivity of your VCAP_SERVICES and take note of the values of the fields
    • credentials.onpremise_proxy_port
    • credentials.onpremise_proxy_host
note

We'll refer to these values as proxy-port and proxy-host hereafter.

  • Create an SSH session to your app container with the following command and let the session opened:
cf ssh app-name -L proxy-port:proxy-host:proxy-port
  • Replace the value of the field VCAP_SERVICES.connectivity.credentials.onpremise_proxy_host in your default-env.json with localhost.
Ready

Now your local setup is ready to consume an On-premise HTTP destination.

Last updated on by KavithaSiva