com.sap.cloud.yaas.servicesdk.springboot.clients.jersey.JerseyClientAutoConfiguration Maven / Gradle / Ivy
/*
* © 2017 SAP SE or an SAP affiliate company.
* All rights reserved.
* Please see http://www.sap.com/corporate-en/legal/copyright/index.epx for additional trademark information and
* notices.
*/
package com.sap.cloud.yaas.servicesdk.springboot.clients.jersey;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.WriterInterceptor;
import com.sap.cloud.yaas.servicesdk.authorization.AccessTokenProvider;
import com.sap.cloud.yaas.servicesdk.authorization.cache.SimpleCachingProviderWrapper;
import com.sap.cloud.yaas.servicesdk.authorization.integration.jaxrs.OAuth2Filter;
import com.sap.cloud.yaas.servicesdk.authorization.protocol.ClientCredentialsGrantProvider;
import com.sap.cloud.yaas.servicesdk.jerseysupport.logging.RequestResponseLoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.util.Map;
/**
* Auto Configuration class, registering Jersey-specific features related to the client side.
*
* Currently it contains Jersey-based {@link RequestResponseLoggingFilter} that logs the request and response (with
* masking Authorization headers), and an instance of {@link AccessTokenProvider} for OAuth2 authorization using the
* Yaas Service SDK authorization library.
*/
@Configuration
@ConditionalOnClass({Client.class, ClientBuilder.class, ResourceConfig.class})
@AutoConfigureAfter(JacksonAutoConfiguration.class)
@EnableConfigurationProperties({JerseyClientProperties.class, OAuth2ClientProperties.class})
@PropertySource("classpath:/com/sap/cloud/yaas/servicesdk/springboot/clients/jersey/jersey-client-default.properties")
public class JerseyClientAutoConfiguration implements ApplicationContextAware
{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Registers the {@link StringToAuthorizationScopeConverter} bean.
*
* @return the {@link StringToAuthorizationScopeConverter} instance
*/
@Bean
@ConfigurationPropertiesBinding
public StringToAuthorizationScopeConverter stringToAuthorizationScopeConverter()
{
return new StringToAuthorizationScopeConverter();
}
/**
* Registers new JAX-WS {@link Feature} that registers the {@link RequestResponseLoggingFilter} to the
* JAX-RS {@link FeatureContext}.
*
* @param jerseyProps the properties that contain the logger and the maximum entity size
* @return the new {@link Feature}
*/
@Bean
@ConditionalOnClass({RequestResponseLoggingFilter.class, LoggerFactory.class, Logger.class})
@ConditionalOnProperty(prefix = "yaas.clients.jersey", name = "request-response-logging.logger-name")
public Feature serviceRequestResponseLoggingFilter(final JerseyClientProperties jerseyProps)
{
return new Feature()
{
@Override
public boolean configure(final FeatureContext context)
{
final Logger log = LoggerFactory.getLogger(jerseyProps.getRequestResponseLogging().getLoggerName());
final int maxEntitySize = jerseyProps.getRequestResponseLogging().getMaxEntitySize();
final RequestResponseLoggingFilter filter = maxEntitySize > 0
? new RequestResponseLoggingFilter(log, maxEntitySize)
: new RequestResponseLoggingFilter(log);
context.register(
filter,
ClientRequestFilter.class,
ClientResponseFilter.class,
WriterInterceptor.class);
return true;
}
};
}
/**
* If not yet registered, registers a default caching instance of {@link AccessTokenProvider}. That will take
* care of requesting Oauth2 token.
*
* @param jaxRsProps Jersey specific properties that should be applied to the Jersey client that is calling the
* authorization server
* @param oAuth2Props properties containing client authorization information
* @return an instance of {@link AccessTokenProvider} that provides access token caching
*/
@Bean
@ConditionalOnMissingBean(AccessTokenProvider.class)
@ConditionalOnClass({ClientCredentialsGrantProvider.class, SimpleCachingProviderWrapper.class})
public AccessTokenProvider oAuth2ClientCredentialsGrantProviderWithCaching(
final JerseyClientProperties jaxRsProps,
final OAuth2ClientProperties oAuth2Props)
{
return new SimpleCachingProviderWrapper(createClientCredentialsGrantProvider(jaxRsProps, oAuth2Props));
}
private ClientCredentialsGrantProvider createClientCredentialsGrantProvider(
final JerseyClientProperties jaxRsProps,
final OAuth2ClientProperties oAuth2Props)
{
final Client client = createJerseyClientBuilder(jaxRsProps).build();
final ClientCredentialsGrantProvider accessTokenProvider = new ClientCredentialsGrantProvider(client);
accessTokenProvider.setTokenEndpointUri(oAuth2Props.getTokenEndpointUri());
accessTokenProvider.setClientId(oAuth2Props.getClientId());
accessTokenProvider.setClientSecret(oAuth2Props.getClientSecret());
accessTokenProvider.setScopeValidationEnabled(oAuth2Props.getScopeValidationEnabled());
return accessTokenProvider;
}
/**
* Registers an instance of {@link OAuth2Filter} that can be used for automatically adding Authorization header
* to requests as well as requesting and caching the OAuth2 token.
*
* @param oAuth2Props the configuration related to the authorization token retrieval
* @param accessTokenProvider an instance of {@link AccessTokenProvider}, that will make the actual calls to the
* authorization server
* @return an instance of {@link OAuth2Filter}
*/
@Bean
@ConditionalOnClass(OAuth2Filter.class)
@ConditionalOnBean(AccessTokenProvider.class)
public OAuth2Filter jaxRsOAuth2Filter(final OAuth2ClientProperties oAuth2Props, final AccessTokenProvider accessTokenProvider)
{
return new OAuth2Filter(
accessTokenProvider,
oAuth2Props.getDefaultScope(),
oAuth2Props.getTokenRequestRetries());
}
/**
* Creates an instance of Jersey HTTP {@link Client}, with given {@link OAuth2Filter} that will take care of
* automatically authenticating the requests.
*
* @param properties the Jersey configuration for the client
* @param oAuth2Filter the authorization related configuration properties
* @return an instance of Jersey {@link Client}
*/
@Bean
@ConditionalOnMissingBean(Client.class)
@ConditionalOnBean(OAuth2Filter.class)
public Client jerseyClient(final JerseyClientProperties properties, final OAuth2Filter oAuth2Filter)
{
final ClientBuilder clientBuilder = createJerseyClientBuilder(properties);
clientBuilder.register(oAuth2Filter);
return clientBuilder.build();
}
/**
* Creates an instance of Jersey HTTP {@link Client}, with given {@link OAuth2Filter} that will take care of
* automatically authenticating the requests.
*
* Will be called only if {@link OAuth2Filter} has not been instantiated (for example because it's not on the
* classpath).
*
* @param properties the Jersey configuration for the client
* @return an instance of Jersey {@link Client}
*/
@Bean
@ConditionalOnMissingBean({Client.class, OAuth2Filter.class})
public Client jerseyClient(final JerseyClientProperties properties)
{
return createJerseyClientBuilder(properties).build();
}
/**
* Internal factory method to create a Jersey {@link ClientBuilder}.
* @param properties the properties to be used for configuration
* @return instance of {@link ClientBuilder}
*/
ClientBuilder createJerseyClientBuilder(final JerseyClientProperties properties)
{
final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
for (final Map.Entry configProperty : properties.getConfig().entrySet())
{
clientBuilder.property(
"jersey.config." + configProperty.getKey(),
configProperty.getValue());
}
final Map features = applicationContext.getBeansOfType(Feature.class);
for (final Feature feature : features.values())
{
clientBuilder.register(feature);
}
return clientBuilder;
}
}