All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.sdk.cloudplatform.connectivity.ScpCfDestinationFacade Maven / Gradle / Ivy

Go to download

Implementation of the Cloud platform abstraction for general-purpose connectivity on the SAP Cloud Platform (Cloud Foundry).

The newest version!
/*
 * Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved.
 */

package com.sap.cloud.sdk.cloudplatform.connectivity;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.json.JsonSanitizer;
import com.sap.cloud.sdk.cloudplatform.cache.CacheKey;
import com.sap.cloud.sdk.cloudplatform.cache.CacheManager;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.cloudplatform.security.user.ScpCfUserFacade;
import com.sap.cloud.sdk.cloudplatform.security.user.User;
import com.sap.cloud.sdk.cloudplatform.security.user.UserAccessor;
import com.sap.cloud.sdk.cloudplatform.tenant.Tenant;
import com.sap.cloud.sdk.cloudplatform.tenant.TenantAccessor;

import lombok.RequiredArgsConstructor;

/**
 * Implementation of Cloud platform abstraction for destinations on SAP Cloud Platform, Cloud Foundry.
 * 

* In addition to reading destinations via the SAP CP CF destination service REST API , this class provides the ability * to read HTTP destination information from the environment variable "destinations". The environment variable is * expected to be in JSON format (see {@link AbstractDestinationFacade}). */ @RequiredArgsConstructor public class ScpCfDestinationFacade extends AbstractDestinationFacade { private static final Logger logger = CloudLoggerFactory.getLogger(ScpCfDestinationFacade.class); @Nonnull private final DestinationService destinationService; @Nonnull private final XsuaaService xsuaaService; /** * Creates a new default facade. */ public ScpCfDestinationFacade() { this(new DestinationService(), new XsuaaService()); } private Map parseDestinations( final String responsePayload, final boolean providerTenantUsed ) throws DestinationAccessException { final JsonArray destinationConfigurations = new JsonParser().parse(JsonSanitizer.sanitize(responsePayload)).getAsJsonArray(); final Map destinations = new HashMap<>(); final ScpCfDestinationFactory destinationFactory = new ScpCfDestinationFactory(); for( final JsonElement destinationConfiguration : destinationConfigurations ) { if( destinationConfiguration.isJsonObject() ) { final GenericDestination destination = destinationFactory.create(new ScpCfDestinationParser(destinationConfiguration.getAsJsonObject())); if( destination instanceof ScpCfDestination ) { if( logger.isDebugEnabled() ) { logger.debug( String.format( "Cloud Foundry Destination %s is of HTTP type, " + "which supports provider/subscriber retrieval strategies.", destination.getName())); } ((ScpCfDestination) destination).setUsingProviderTenant(providerTenantUsed); } else { if( logger.isDebugEnabled() ) { logger.debug( String.format( "Cloud Foundry Destination %s is of non-HTTP type. " + "Provider/subscriber retrieval strategies are not supported, " + "meaning only the subscriber's destinations will be retrieved!", destination.getName())); } } if( logger.isDebugEnabled() ) { logger.debug( String.format( "Adding %s destination %s.", destination.getDestinationType().toString(), destination.getName())); } destinations.put(destination.getName(), destination); } else { if( logger.isWarnEnabled() ) { logger.warn("Ignoring destination configuration: not a JSON object."); } } } return destinations; } static final Cache> destinationsCache = CacheManager.register( CacheBuilder .newBuilder() .concurrencyLevel(10) .maximumSize(100000) .expireAfterWrite(5, TimeUnit.MINUTES) .build()); CacheKey getCacheKey() { // TODO test isolation return CacheKey.of( TenantAccessor.getCurrentTenantIfAvailable().map(Tenant::getTenantId).orElse(null), UserAccessor.getCurrentUserIfAuthenticated().map(User::getName).orElse(null)); } /** * {@inheritDoc} */ @Nonnull @Override public Class getGenericDestinationClass() { return ScpCfGenericDestination.class; } /** * {@inheritDoc} */ @Nonnull @Override public Class getDestinationClass() { return ScpCfDestination.class; } /** * {@inheritDoc} */ @Nonnull @Override public Class getRfcDestinationClass() { return ScpCfRfcDestination.class; } /** * {@inheritDoc} */ @Nonnull @Override public Map getGenericDestinationsByName() throws DestinationAccessException { if( getDestinationsFromEnvironmentVariable() != null ) { if( logger.isWarnEnabled() ) { logger.warn( "Environment variable '" + AbstractDestinationFacade.VARIABLE_DESTINATIONS + "' is set. " + "Destinations will only be read from this variable. " + "Unset this variable to read destinations from the destination service on SAP Cloud Platform."); } return getDestinationsFromEnvironmentVariable(new ScpCfDestinationFactory()); } final CacheKey cacheKey = getCacheKey(); try { return destinationsCache.get(cacheKey, () -> { final Map result = new HashMap<>(); final Map providerDestinations = fetchAllDestinations(true); final Map subscriberDestinations = fetchAllDestinations(false); for( final String destinationName : Sets .union(providerDestinations.keySet(), subscriberDestinations.keySet()) ) { final GenericDestination chosenDestination = chooseDestinationByStrategy( destinationName, providerDestinations.get(destinationName), subscriberDestinations.get(destinationName)); if( chosenDestination != null ) { result.put(destinationName, chosenDestination); } } return result; }); } catch( final ExecutionException | UncheckedExecutionException e ) { throw new DestinationAccessException(e.getMessage(), e); } } @Nonnull private Map fetchAllDestinations( final boolean useProviderTenant ) throws DestinationAccessException { final DestinationServiceCommand subaccountDestinationCommand = new DestinationServiceCommand( xsuaaService, false, destinationService, "subaccountDestinations", useProviderTenant); final DestinationServiceCommand instanceDestinationCommand = new DestinationServiceCommand( xsuaaService, false, destinationService, "instanceDestinations", useProviderTenant); final Map result = new HashMap<>(); try { final Future subaccountDestinations = subaccountDestinationCommand.queue(); final Future instanceDestinations = instanceDestinationCommand.queue(); result.putAll(parseDestinations(subaccountDestinations.get(), useProviderTenant)); result.putAll(parseDestinations(instanceDestinations.get(), useProviderTenant)); } catch( final ExecutionException | UncheckedExecutionException | InterruptedException e ) { // quick noop test final String tenantString = useProviderTenant ? "Provider" : "Subscriber"; if( logger.isDebugEnabled() ) { logger.debug( "Failed to fetch any destination for the " + tenantString + " Tenant. See the following stacktrace for more information.", buildDestinationAccessException( subaccountDestinationCommand, instanceDestinationCommand, useProviderTenant, e)); } else { logger.info( "Failed to fetch any destination for the " + tenantString + " Tenant. This is not necessarily a problem on it's own. However, if you expect to retrieve destinations from the " + tenantString + " set your log level to DEBUG to see the causing stack trace."); } } return result; } @Nullable private GenericDestination chooseDestinationByStrategy( @Nonnull final String destinationName, @Nullable final GenericDestination providerDestination, @Nullable final GenericDestination subscriberDestination ) { switch( DestinationAccessor.getRetrievalStrategy(destinationName) ) { case PLATFORM_DEFAULT: case ALWAYS_SUBSCRIBER: { return subscriberDestination; } case ALWAYS_PROVIDER: { return providerDestination; } case SUBSCRIBER_THEN_PROVIDER: { if( subscriberDestination != null ) { return subscriberDestination; } else { return providerDestination; } } } return null; } @Nonnull private DestinationAccessException buildDestinationAccessException( @Nonnull final DestinationServiceCommand subaccountDestinationCommand, @Nonnull final DestinationServiceCommand instanceDestinationCommand, final boolean providerTenantUsed, @Nonnull final Throwable exceptionThrown ) { final DestinationServiceCommand failedCommand = subaccountDestinationCommand.isFailedExecution() ? subaccountDestinationCommand : instanceDestinationCommand.isFailedExecution() ? instanceDestinationCommand : null; final String message; final String reason; if( failedCommand != null ) { message = String.format( "Failed to get destinations of %s %s", providerTenantUsed ? "provider" : "subscriber", instanceDestinationCommand.isFailedExecution() ? "service instance" : ""); reason = failedCommand.getExecutionException().getMessage(); } else { message = "Failed to get destinations"; reason = exceptionThrown.getCause() != null ? exceptionThrown.getCause().getMessage() : exceptionThrown.getMessage(); } return new DestinationAccessException( message + (reason == null ? "." : ": " + (reason.endsWith(".") ? reason : reason + ".")) + " If your application is running on Cloud Foundry, " + "make sure to have a binding to both the destination " + "service and the authorization and trust management (xsuaa) " + "service, AND that you either properly secured your " + "application or have set the '" + ScpCfUserFacade.VARIABLE_ALLOW_MOCKED_AUTH_HEADER + "' environment variable to true. " + "Please note that authentication types with user propagation, " + "for example, principal propagation or the OAuth2 SAML Bearer flow, " + "require that you secure your application and will not work when using the '" + ScpCfUserFacade.VARIABLE_ALLOW_MOCKED_AUTH_HEADER + "' environment variable. " + "If your application is not running on Cloud Foundry, " + "for example, when deploying to a local container, " + "consider declaring the '" + VARIABLE_DESTINATIONS + "' environment variable to configure destinations.", exceptionThrown); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy