Connecting To Bound Services
The SAP Cloud SDK supports connecting to arbitrary services that are bound to an application via a service binding. Service Bindings provide an application with the necessary credentials to connect to a service when deployed on the SAP Business Technology Platform.
Connecting to a Service
You can obtain a Destination
to connect to a service by using the ServiceBindingDestinationLoader
API:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.<MY_SERVICE>)
.build();
HttpDestination destination = ServiceBindingDestinationLoader.defaultLoaderChain()
.getDestination(options);
Choose from the available ServiceIdentifier
constants to specify the service you want to connect to.
For example, for connecting to the Workflow REST API use:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.WORKFLOW)
.withOption(BtpServiceOptions.WorkflowOptions.REST_API)
.build();
HttpDestination destination = ServiceBindingDestinationLoader.defaultLoaderChain()
.getDestination(options);
Note the additional option BtpServiceOptions.WorkflowOptions.REST_API
that is specific to the Workflow API (REST).
This is an example where the service offers multiple API endpoints and you need to define which endpoint the resulting HttpDestination
should point to.
Explore the BtpServiceOptions class to find the options relevant for your service and your use-case.
List of Supported Services
The SAP Cloud SDK supports a variety of services out of the box. For example, the AI Core service or the Business Rules service. You'll find a full list below.
Full List of Supported Services
The following services are supported out of the box:
- The SAP AI Core Service
- The SAP Extended Service for User and Account Authentication (XSUAA)
- The SAP Identity and Authentication Service (IAS)
- For IAS please see the dedicated section below.
- The SAP Workflow Service on Cloud Foundry
- The SAP Business Rules Service on Cloud Foundry
- The SAP Business Logging on Cloud Foundry (SAP internal)
- Configuration API
- Text API
- Reading API
- Writing API
- The BTP Destination Service on Cloud Foundry
- The BTP Connectivity Service on Cloud Foundry
In case the service you want to connect to is not yet recognized by the SAP Cloud SDK by default, you can still use the API by providing some additional information.
For services backed by the SAP Identity and Authentication Service (IAS), please see the section on IAS below. For other services, please refer to the section on adding support for more services.
In case you are a service provider and want to make your service compatible with the SAP Cloud SDK: Please consider contributing!
- For IAS-based services it is sufficient to conform to the dedicated service binding format for IAS.
- For XSUAA-based services please refer to the section on adding support for more services and consider adding your implementation here by raising a pull request.
Working with Multiple Service Bindings
In case your application is bound to multiple instances of the same service, passing the ServiceIdentifier
is not sufficient.
Instead, you need to provide the ServiceBinding
instance directly.
var bindingList = DefaultServiceBindingAccessor.getInstance()
.getServiceBindings();
// select the desired binding from the list of all available bindings
ServiceBinding binding = bindingList.get(0);
ServiceBindingDestinationOptions
.forService(binding)
.build()
Multitenancy and Principal Propagation
Destinations created via the ServiceBindingDestinationLoader
API are tenant-aware by default.
That means authorization to the target system will by default be performed on behalf of the current tenant at the time of request execution.
This can be customized by passing a different value for OnBehalfOf
.
For example, to propagate the current user to the target system use:
ServiceBindingDestinationOptions
.forService(ServiceIdentifier.WORKFLOW)
.withOption(BtpServiceOptions.WorkflowOptions.REST_API)
.onBehalfOf(OnBehalfOf.NAMED_USER_CURRENT_TENANT)
.build();
Using the Extended Service for User and Account Authentication (XSUAA)
Communicating with SAP provided services secured by the SAP XSUAA service usually requires explicit support by the SAP Cloud SDK (see list of supported services). This is because those services are secured by their own instance of XSUAA and, therefore, have their own authentication configuration. The configuration is contained in the service binding of the respective service.
In scenarios where services are secured by a shared instance of the SAP XSUAA service, however, the SAP Cloud SDK can be used without explicit support:
ServiceBindingDestinationOptions
.forService(ServiceIdentifier.XSUAA)
.withOption(BtpServiceOptions.AuthenticationServiceOptions.withTargetUri("https://foo.com"))
.build();
The code above instructs the SAP Cloud SDK to
- (Line 2) create a destination towards the XSUAA instance, which the application itself is bound to and
- (Line 3) use the manually provided URI (
https://foo.com
) as the system to communicate with.
This configuration results in a destination that uses the XSUAA instance of your application to authenticate against, but communicates with the system reachable under the provided URI. Without the option specified in line 3, the destination would target the XSUAA instance itself.
For IAS-based applications and services principal propagation requires additional configuration.
When creating the IAS service binding an additional parameter needs to be passed to enable the jwt-bearer
grant type.
Refer to the documentation here (SAP-internal).
Using the Identity and Authentication Service (IAS)
The API for connecting to services secured by the SAP Identity and Authentication Service (IAS) is currently in beta and subject to change.
In case your application is bound to an instance of the SAP Identity and Authentication Service (IAS) you can use the SAP Cloud SDK to connect to other applications and services that are secured using IAS. Effectively, the SAP Cloud SDK implements the OAuth flows described here.
The SAP Cloud SDK supports the credential types binding-secret
, X509_GENERATED
and X509_ATTESTED
for IAS service bindings.
If you want to use the X509_ATTESTED
credential type, you need to add the connectivity-ztis
dependency to your project.
Read more about how to configure your app for this credential type on the documentation for using certificates from the Zero Trust Identity Service (ZTIS).
The type X509_PROVIDED
is currently not supported.
Connecting to Services
If your service is secured using IAS and is using the dedicated service binding format supported by the SAP Cloud SDK, you can obtain a destination by passing the service label as the ServiceIdentifier
:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.of("your-service-label"))
.build();
In case your service is not using the default format you can still use the IasOptions
and AuthenticationServiceOptions
to provide the necessary information:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.IDENTITY_AUTHENTICATION)
.withOption(BtpServiceOptions.AuthenticationServiceOptions.withTargetUri("https://foo.com"))
.build();
Obtain the URL from the service binding of the service you want to connect to.
In case the service does not require a JWT token (e.g. the Event Broker service) you can pass an additional option to skip the JWT token flow:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.IDENTITY_AUTHENTICATION)
.withOption(BtpServiceOptions.AuthenticationServiceOptions.withTargetUri("https://foo.com"))
.withOption(BtpServiceOptions.IasOptions.withoutTokenForTechnicalProviderUser())
.onBehalfOf(OnBehalfOf.TECHNICAL_USER_PROVIDER)
.build();
This is a moderate performance improvement, but not substantial, since tokens are of course cached.
Please note that this only applies when authenticating on behalf of the provider tenant using an X.509 Certificate. For authenticating on behalf of a subscriber tenant or a when propagating user information, a JWT is always required to carry the additional tenant/user information.
Connecting to Applications
In case you want to connect to a system that is registered as an application within IAS use the IasOptions.withApplicationName
to provide the application name:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.IDENTITY_AUTHENTICATION)
.withOption(BtpServiceOptions.IasOptions.withApplicationName("application-name"))
.withOption(BtpServiceOptions.AuthenticationServiceOptions.withTargetUri("https://foo.com"))
.build();
Calling Back Applications
If you received an incoming request from an application using IAS you can use the following options to create a destination for calling back the application:
var options = ServiceBindingDestinationOptions
.forService(ServiceIdentifier.IDENTITY_AUTHENTICATION)
.withOption(BtpServiceOptions.IasOptions.withConsumerClient("client-id"))
.withOption(BtpServiceOptions.AuthenticationServiceOptions.withTargetUri("https://foo.com"))
.build();
Use the client ID you received in the JWT of the incoming request.
Service Binding Format
The SAP Cloud SDK expects services backed by IAS to have the following service binding format:
- The
credentials
section must contain an objectauthentication-service
with at least one fieldservice-label
set toidentity
. - The
credentials
section must contain an objectendpoints
which must contain at least one entry.- Each entry under
endpoints
must be an object containing auri
field set to a string.
- Each entry under
- Further fields under
authentication-service
andendpoints
are optional as shown below - Aside from these requirements the service binding may contain any other fields as needed.
For example, the following binding would be recognized:
{
"label": "eventmesh-sap2sap-internal",
"credentials": {
"authentication-service": {
"service-label": "identity"
},
"endpoints": {
"eventing-endpoint": {
"uri": "https://http-gateway.eu11.beb.em.services.cloud.sap",
"always-requires-token": false
}
}
}
}
Full format with optional fields
{
// the usual binding metadata fields here
// ...
"credentials": {
"authentication-service": {
"service-label": "identity",
"app-name": "app" // optional
},
"endpoints": {
"my-endpoint": {
"uri": "https://cert.service.com",
// the following fields are optional and shown here with their assumed default values
"protocol": "http",
"always-requires-token": true,
"requires-mtls": true
},
// multiple endpoints can be defined in addition
"my-other-endpoint": {
"uri": "https://service.com",
"requires-mtls": false
},
"my-ftp-endpoint": {
"uri": "ftp://service.com",
"protocol": "ftp"
}
}
}
}
Currently, the SAP Cloud SDK only supports service bindings with a single entry under endpoints
.
Adding Support for More Services
In case you want to connect to a service that is not supported out of the box, you can still use the ServiceBindingDestinationLoader
API by providing some additional information.
In most cases it's sufficient to provide an implementation of OAuth2PropertySupplier
that can extract the required information from the specific service binding.
Services backed by XSUAA typically have rather similar service binding formats that differ only slightly.
For services backed by IAS please refer to the sections above instead.
In most cases overriding the DefaultOAuth2PropertySupplier
and implementing the getServiceUri()
method is already sufficient.
For example, assume the following binding structure:
{
"label": "my-service",
"credentials": {
"some": {
"json": {
"path": "https://my-service.com/"
}
},
"clientid": "my-client-id"
// ...
}
}
This could be handled by the following implementation:
class MyServicePropertySupplier extends DefaultOAuth2PropertySupplier
{
MyServicePropertySupplier( @Nonnull final ServiceBindingDestinationOptions options )
{
super(options, Collections.emptyList());
}
@Nonnull
@Override
public URI getServiceUri()
{
return getCredentialOrThrow(URI.class, "some", "json", "path");
}
}
OAuth2ServiceBindingDestinationLoader.registerPropertySupplier(
ServiceIdentifier.of("my-service"), MyServicePropertySupplier::new);
Note that:
- Line 5 indicates that the OAuth credentials are stored directly in the
credentials
object. - Line 12 extracts the service URI from the
credentials.some.json.path
field. - Line 17 registers the implementation for any binding with the label
my-service
.
For more details refer to the more extensive guidance given in the advanced section below. Also consider the existing implementations and tests.
Local Development
When developing locally you have various options to provide the necessary service binding information:
- When using CAP you can use the Hybrid Testing feature.
- You can manually copy the
VCAP_SERVICES
environment variable and set it locally (e.g. in your terminal or IDE or using an IDE plugin that loads a.env
file) - You can define a binding programmatically in your application (e.g. in a test setup or a local development profile)
Programmatically Defining a Service Binding
You can create a ServiceBinding
instance programmatically and use it to create a destination:
var binding = new DefaultServiceBindingBuilder()
.withServiceIdentifier(ZTIS_IDENTIFIER)
.withCredentials(Map.of(
"clientid", "id",
"url", "https://foo.com"))
.build();
You can pass the binding explicitly via the options as described in the section on working with multiple bindings.
However, in some cases the SAP Cloud SDK or other frameworks may implicitly access service bindings.
For such cases one can modify the global ServiceBindingAccessor
.
The SAP Cloud SDK will use the default instance whenever looking for service bindings. You can override this instance:
DefaultServiceBindingAccessor.setInstance(() -> List.of(binding));
In case you want to keep all existing bindings and just add your own you can use the following code:
final ServiceBindingAccessor accessor = DefaultServiceBindingAccessor.getInstance();
var newAccessor = new ServiceBindingMerger(List.of(accessor, () -> List.of(binding)), ServiceBindingMerger.KEEP_EVERYTHING);
DefaultServiceBindingAccessor.setInstance(newAccessor);
(Advanced) Background Information
In general, the ServiceBindingDestinationLoader
API can be used to convert ServiceBinding
instances into HttpDestination
instances.
It comes as part of the cloudplatform-connectivity
artifact.
The API is designed for easy extensibility and customization.
Hereby, each ServiceBindingDestinationLoader
implementation is supposed to provide the transformation logic for one specific "type" of ServiceBinding
.
The SAP Cloud SDK provides, for example, an implementation for service bindings that contain OAuth2 credentials.
As an input, the API requires the ServiceBindingDestinationOptions
instance.
This class is designed to hold all information that is required to transform a ServiceBinding
into an HttpDestination
- this, first and foremost, includes the ServiceBinding
itself.
Furthermore, there is also a chain implementation that can be used to combine multiple ServiceBindingDestinationLoader
implementations into one.
The chain will try to load a destination using the given ServiceBindingDestinationLoader
delegates until the first of them succeeds - much like a fallback mechanism.
This concept is also reflected in the example from above, where the defaultLoaderChain
is used to load a destination.
About the Options
The ServiceBindingDestinationOptions
is an extensible and type-safe collection of parameters that can be used to configure the ServiceBinding
to HttpDestination
transformation process.
Hereby, the least information that is needed is the ServiceBinding
itself, which can be provided using the forService(ServiceBinding)
method.
Alternatively, you can also use forService(ServiceIdentifier)
.
This method will try to find a ServiceBinding
for the given ServiceIdentifier
using the DefaultServiceBindingAccessor
instance.
Please note: If there are multiple or no ServiceBinding
s for the given ServiceIdentifier
, an exception will be thrown.
Besides the ServiceBinding
, there is a second parameter that is shared between all ServiceBindingDestinationLoader
implementations: The OnBehalfOf
, for which the destination should be loaded.
This can be set using the onBehalfOf(OnBehalfOf)
method.
If not explicitly specified, OnBehalfOf.TECHNICAL_USER_CURRENT_TENANT
is used.
Custom Parameters
Adding custom parameters to the ServiceBindingDestinationOptions
can be done using the withOption(OptionsEnhancer)
method.
Hereby, an OptionsEnhancer
is a type-safe representation of arbitrary parameters.
For OAuth2 based service bindings, the OAuth2Options.TokenRetrievalTimeout
can be used to customize the timeout for token requests.
The below example sets a timeout of 100 seconds:
ServiceBindingDestinationOptions
.forService(ServiceIdentifier.of("my-service"))
.withOption(
OAuth2Options.TokenRetrievalTimeout.of(
TimeLimiterConfiguration.of(Duration.ofSeconds(100))))
.build();
Example: Custom Service Binding Transformation
The following example demonstrates rather advanced usage of our ServiceBindingDestinationLoader
API.
A vast majority of use cases do not require a custom ServiceBindingDestinationLoader
implementation.
Instead, customizing the existing OAuth2ServiceBindingDestinationLoader
as described below is most probably sufficient.
Lets assume we are providing a custom ServiceBindingDestinationLoader
implementation that requires some additional information to be able to transform a ServiceBinding
into an HttpDestination
.
In this example, we assume that there are two different APIs for our service.
Depending on which API we want to create our HttpDestination
for, we need to slightly alter the transformation logic.
Additionally, we want to be able to supply an additional String
parameter that also affects the created HttpDestination
.
To achieve this behavior, we would need to implement two OptionsEnhancer
s like so:
enum MyApiChoice implements ServiceBindingDestinationOptions.OptionsEnhancer<MyApiChoice> {
API1,
API2;
@Nonnull
@Override
public MyApiChoice getValue() {
return this;
}
}
class MyStringParameter implements ServiceBindingDestinationOptions.OptionsEnhancer<String> {
private final String value;
public static MyStringParameter of(String value) {
return new MyStringParameter(value);
}
private MyStringParameter(String value) {
this.value = value;
}
@Nonnull
@Override
public String getValue() {
return value;
}
}
The first implementation uses an enum
to implement a fixed set of choices option.
It is noteworthy that the actual parameter type is the MyApiChoice
enum itself.
That way, we will be able to retrieve the set value later on.
In contrast to that, the second implementation provides a String
parameter.
Setting the options can then be done like so:
ServiceBinding someServiceBinding;
ServiceBindingDestinationOptions
.forService(someServiceBinding)
.withOption(MyApiChoice.API1)
.withOption(MyStringParameter.of("some-value"))
.build();
Finally, we can implement our custom ServiceBindingDestinationLoader
that uses our new options like so:
class MyCustomServiceBindingLoader implements ServiceBindingDestinationLoader
{
@Nonnull
@Override
public Try<HttpDestination> tryGetDestination( @Nonnull ServiceBindingDestinationOptions options )
{
Option<MyApiChoice> maybeOption = options.getOption(MyApiChoice.class);
if( maybeOption.isEmpty() ) {
return Try.failure(new DestinationNotFoundException("No API choice was specified."));
}
Option<String> maybeStringValue = options.getOption(MyStringParameter.class);
MyApiChoice option = maybeOption.get();
switch( option ) {
case API1:
// do something
break;
case API2:
// do something else
break;
}
}
}
About the OAuth2Loader
The OAuth2ServiceBindingDestinationLoader
comes as part of the connectivity-oauth
artifact and provides a customizable implementation for converting ServiceBinding
s that use OAuth2 authentication into HttpDestination
instances.
More precisely, it supports the OAuth2 Client Credentials and OAuth2 JWT Bearer grant types.
Internally, the implementation performs the following steps:
- Check whether the given
ServiceBindingDestinationOptions
can be transformed (e.g. whether theServiceBinding
has aclientid
). - Extract the required information from the
ServiceBinding
(i.e. the service URL, the token URL, and the client identity). - Assemble an
HttpDestination
that uses the extracted information for the target authentication (i.e. HTTP headerAuthorization
).
Dealing With Arbitrary Service Bindings
Even though service bindings are a fundamental concept in the SAP Business Technology Platform, the way they are used and structured can vary greatly from service to service.
As a consequence, it is not possible to provide a uniform way of dealing with arbitrary ServiceBinding
s.
Therefore, the OAuth2ServiceBindingDestinationLoader
leverages the OAuth2PropertySupplier
interface to extract the required information from a given ServiceBindingDestinationLoaderOptions
instance.
The OAuth2ServiceBindingDestinationLoader
stores a static list of OAuth2PropertySupplier
instances.
Whenever the OAuth2ServiceBindingDestinationLoader
is asked to load a destination, it will iterate over this list (in order) to see whether
- the given
ServiceBindingDestinationOptions
can be transformed into anHttpDestination
using the currentOAuth2PropertySupplier
instance, and, if so - extract the required information from the given
ServiceBindingDestinationOptions
using the currentOAuth2PropertySupplier
.
By default, there are OAuth2PropertySupplier
for the services mentioned above in place.
Customization
In case the default configuration of the OAuth2ServiceBindingDestinationLoader
does not fit your needs, you can customize it by adding your own OAuth2PropertySupplier
instances to the static list of the OAuth2ServiceBindingDestinationLoader
:
OAuth2ServiceBindingDestinationLoader.registerPropertySupplier(this::canBeConverted, this::propertySupplierFactory);
boolean canBeConverted(ServiceBindingDestinationOptions options);
OAuth2PropertySupplier propertySupplierFactory(ServiceBindingDestinationOptions options);
As shown above, the registerPropertySupplier
method takes two methods as parameters.
This might be unintuitive, but is needed to allow for efficient implementations.
What is happening internally when this method is used, is that the given parameters are transformed into an internal representation, which is then prepended to the list of available OAuth2PropertySupplier
instances.
In other words: Using the registerPropertySupplier
method will make your OAuth2PropertySupplier
instance be considered before any previously registered OAuth2PropertySupplier
instance, including the default ones.
Full Example
To make things a bit more concrete, let's assume we wanted to implement support for an other service, which uses its own ServiceBinding
structure.
Our hypothetical service binding could look like this:
{
"service": "my-service",
"credentials": {
"service": {
"read": "https://my-service.com/api/v1/read",
"write": "https://my-service.com/api/v2/write"
},
"oauth": {
"uri": "https://my-service.com/oauth/token",
"id": "my-client-id",
"secret": "my-client-secret"
}
}
}
The service binding above contains two different API endpoints (read
and write
), which we would like to be able to access using the OAuth2ServiceBindingDestinationLoader
.
To support that, we need to implement an OAuth2PropertySupplier
(to implement the property extraction logic) and an OptionsEnhancer
(to allow users to define which endpoint they would like to use):
enum MyApiEndpoint implements ServiceBindingDestinationOptions.OptionsEnhancer<MyApiEndpoint> {
READ,
WRITE;
@Nonnull
@Override
public MyApiEndpoint getValue() {
return this;
}
}
class MyServiceOAuth2PropertySupplier extends DefaultOAuth2PropertySupplier
{
public MyServiceOAuth2PropertySupplier(ServiceBindingDestinationOptions options) {
super(options, Collections.singletonList("oauth"));
}
@Override
public boolean isOAuth2Binding() {
return getOAuthCredential(String.class, "id").isDefined();
}
@Nonnull
@Override
public URI getServiceUri() {
MyApiEndpoint endpoint = options.getOption(MyApiEndpoint.class).getOrElse(MyApiEndpoint.READ);
return switch (endpoint) {
case READ -> getCredentialOrThrow(URI.class, "service", "read");
case WRITE -> getCredentialOrThrow(URI.class, "service", "write");
};
}
@Nonnull
@Override
public URI getTokenUri() {
return getOAuthCredentialOrThrow(URI.class, "uri");
}
@Nonnull
@Override
public ClientIdentity getClientIdentity() {
var clientId = getOAuthCredentialOrThrow(String.class, "id");
var clientSecret = getOAuthCredentialOrThrow(String.class, "secret");
return new ClientCredentials(clientId, clientSecret);
}
}
Lets examine the implementation above:
Right in the first line, you can see that our implementation extends the DefaultOAuth2PropertySupplier
class, instead of implementing the OAuth2PropertySupplier
interface directly.
This is because the DefaultOAuth2PropertySupplier
class provides a lot of useful functionality, which we can reuse in our implementation.
We will see that in a second.
Next thing we should pay attention to is the constructor of our class.
It takes an instance of ServiceBindingDestinationOptions
as a parameter.
This instance is then passed to the super
constructor (line 4) along with a list of String
s.
The super class (DefaultOAuth2PropertySupplier
) uses these parameters to
- cache the credentials portion of the
ServiceBinding
(given in theServiceBindingDestinationOptions
) as aTypedMapView
instance and - set the default oauth2 properties path within the credentials portion of the
ServiceBinding
.
Converting the credentials portion of the ServiceBinding
into a TypedMapView
is helpful for parsing the nested structure.
Therefore, it is a good idea to do the conversion (i.e. TypeMapView.ofCredentials(ServiceBinding)
) just once as it performs an expensive deep-copy.
Hereby, the DefaultOAuth2PropertySupplier
takes care of doing the conversion upon initialization and caching the result for later use.
Further down in our code (i.e. within the overridden methods), we can then use the getCredential/OrThrow
and getOAuthCredential/OrThrow
methods to extract the required information from the cached credentials.
The main difference between getCredential
and getOAuthCredential
is that the latter will prepend the default oauth2 properties path to the one given in the method invocation before trying to extract the information.
In our example (line 9), this means that getOAuthCredential(String.class, "id")
will try to extract a String
value from credentials.get("oauth").get("id")
(pseudo code).
In contrast to that, using just getCredential(String.class, "id")
would try to extract a String
value from credentials.get("id")
(pseudo code).
getOAuthCredential
is therefore just a neat shortcut that can help to reduce repetitive code.
Please note how we are handling our MyApiEndpoint
option in the getServiceUri
method (lines 14 - 20).
We are reading the option by providing the MyApiEndpoint.class
as the parameter to the getOption
method.
That way, we can decide which service URI to use, depending on the user input.
Lastly, we need to register our OAuth2PropertySupplier
implementation with the OAuth2ServiceBindingDestinationLoader
:
class MyServiceOAuth2PropertySupplier extends DefaultOAuth2PropertySupplier {
private static final ServiceIdentifier MY_SERVICE_IDENTIFIER = ServiceIdentifier.of("my-service");
public static boolean matches(ServiceBindingDestinationOptions options) {
var serviceIdentifier = options.getServiceBinding().getServiceIdentifier().orElse(null);
return MY_SERVICE_IDENTIFIER.equals(serviceIdentifier);
}
// skipped for brevity
}
OAuth2ServiceBindingDestinationLoader.registerPropertySupplier(MyServiceOAuth2PropertySupplier::matches, MyServiceOAuth2PropertySupplier::new);
In the above example, we added a static matches
method (lines 4 - 7) to our OAuth2PropertySupplier
implementation, which we can use to check whether the given ServiceBindingDestinationOptions
can be transformed into an HttpDestination
using our implementation.
We are then using method references to register our implementation with the OAuth2ServiceBindingDestinationLoader
.
About the Chain Implementation
As mentioned above, the ServiceBindingDestinationLoader
comes with a private chain implementation.
The default instance of that chain (ServiceBindingDestinationLoader.defaultLoaderChain
) is created using the Service Locator Pattern to find all available ServiceBindingDestinationLoader
implementations on the classpath.
Instances of these implementations are then used as delegate loaders.
When transforming a ServiceBindingDestinationOptions
into an HttpDestination
, the chain will invoke the delegates in order until the first one either succeeds or throws an exception other than DestinationNotFoundException
.
The Service Locator Pattern does not provide any guarantees about the order in which implementations on the classpath are found.
Therefore, it is crucial that ServiceBindingDestinationLoader
implementations do not handle the same ServiceBindingDestinationOptions
as otherwise the default chain's behavior would be unpredictable.
Implementations provided by the SAP Cloud SDK follow this rule.
Custom Chain Instances
If the default chain implementation does not meet your needs, you can create your own chain instance:
ServiceBindingDestinationLoader myChain = ServiceBindingDestinationLoader.newLoaderChain(
Arrays.asList(
new MyFirstLoader(),
new MySecondLoader()
));
The code above initializes a new instance of the private chain implementation, which uses the given ServiceBindingDestinationLoader
instances in the specified order as its delegates.
Therefore, deterministic behavior is guaranteed.