Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions spring-cloud-aws-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
<artifactId>spring-cloud-aws-ses</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-sns</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,15 @@
import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails;
import io.awspring.cloud.autoconfigure.core.AwsProperties;
import io.awspring.cloud.autoconfigure.s3.properties.S3Properties;
import io.awspring.cloud.s3.InMemoryBufferingS3OutputStreamProvider;
import io.awspring.cloud.s3.Jackson2JsonS3ObjectConverter;
import io.awspring.cloud.s3.PropertiesS3ObjectContentTypeResolver;
import io.awspring.cloud.s3.S3ObjectContentTypeResolver;
import io.awspring.cloud.s3.S3ObjectConverter;
import io.awspring.cloud.s3.S3Operations;
import io.awspring.cloud.s3.S3OutputStreamProvider;
import io.awspring.cloud.s3.S3ProtocolResolver;
import io.awspring.cloud.s3.S3Template;
import io.awspring.cloud.s3.*;
import java.util.Optional;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
Expand All @@ -49,6 +45,7 @@
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.encryption.s3.S3EncryptionClient;
import tools.jackson.databind.json.JsonMapper;

/**
* {@link AutoConfiguration} for {@link S3Client} and {@link S3ProtocolResolver}.
Expand Down Expand Up @@ -124,11 +121,50 @@ else if (awsProperties.getEndpoint() != null) {
return builder.build();
}

@Bean
@ConditionalOnMissingBean
S3Client s3Client(S3ClientBuilder s3ClientBuilder) {
return s3ClientBuilder.build();
}

@Bean
@ConditionalOnMissingBean
S3OutputStreamProvider inMemoryBufferingS3StreamProvider(S3Client s3Client,
Optional<S3ObjectContentTypeResolver> contentTypeResolver) {
return new InMemoryBufferingS3OutputStreamProvider(s3Client,
contentTypeResolver.orElseGet(PropertiesS3ObjectContentTypeResolver::new));
}

@Conditional(S3EncryptionConditional.class)
@ConditionalOnClass(name = "software.amazon.encryption.s3.S3EncryptionClient")
@Configuration
public static class S3EncryptionConfiguration {

private static void configureEncryptionProperties(S3Properties properties,
ObjectProvider<S3RsaProvider> rsaProvider, ObjectProvider<S3AesProvider> aesProvider,
S3EncryptionClient.Builder builder) {
PropertyMapper propertyMapper = PropertyMapper.get();
var encryptionProperties = properties.getEncryption();

propertyMapper.from(encryptionProperties::isEnableDelayedAuthenticationMode)
.to(builder::enableDelayedAuthenticationMode);
propertyMapper.from(encryptionProperties::isEnableLegacyUnauthenticatedModes)
.to(builder::enableLegacyUnauthenticatedModes);
propertyMapper.from(encryptionProperties::isEnableMultipartPutObject).to(builder::enableMultipartPutObject);

if (!StringUtils.hasText(properties.getEncryption().getKeyId())) {
if (aesProvider.getIfAvailable() != null) {
builder.aesKey(aesProvider.getObject().generateSecretKey());
}
else {
builder.rsaKeyPair(rsaProvider.getObject().generateKeyPair());
}
}
else {
propertyMapper.from(encryptionProperties::getKeyId).to(builder::kmsKeyId);
}
}

@Bean
@ConditionalOnMissingBean
S3Client s3EncryptionClient(S3EncryptionClient.Builder s3EncryptionBuilder, S3ClientBuilder s3ClientBuilder) {
Expand All @@ -154,55 +190,28 @@ S3EncryptionClient.Builder s3EncrpytionClientBuilder(S3Properties properties,
configureEncryptionProperties(properties, rsaProvider, aesProvider, builder);
return builder;
}
}

private static void configureEncryptionProperties(S3Properties properties,
ObjectProvider<S3RsaProvider> rsaProvider, ObjectProvider<S3AesProvider> aesProvider,
S3EncryptionClient.Builder builder) {
PropertyMapper propertyMapper = PropertyMapper.get();
var encryptionProperties = properties.getEncryption();

propertyMapper.from(encryptionProperties::isEnableDelayedAuthenticationMode)
.to(builder::enableDelayedAuthenticationMode);
propertyMapper.from(encryptionProperties::isEnableLegacyUnauthenticatedModes)
.to(builder::enableLegacyUnauthenticatedModes);
propertyMapper.from(encryptionProperties::isEnableMultipartPutObject).to(builder::enableMultipartPutObject);
@Configuration
@AutoConfigureAfter(Jackson2JsonS3ObjectConverterConfiguration.class)
@ConditionalOnClass(value = ObjectMapper.class)
static class LegacyJackson2JsonS3ObjectConverterConfiguration {

if (!StringUtils.hasText(properties.getEncryption().getKeyId())) {
if (aesProvider.getIfAvailable() != null) {
builder.aesKey(aesProvider.getObject().generateSecretKey());
}
else {
builder.rsaKeyPair(rsaProvider.getObject().generateKeyPair());
}
}
else {
propertyMapper.from(encryptionProperties::getKeyId).to(builder::kmsKeyId);
}
@ConditionalOnMissingBean
@Bean
S3ObjectConverter s3ObjectConverter(Optional<ObjectMapper> objectMapper) {
return new LegacyJackson2JsonS3ObjectConverter(objectMapper.orElseGet(ObjectMapper::new));
}
}

@Bean
@ConditionalOnMissingBean
S3Client s3Client(S3ClientBuilder s3ClientBuilder) {
return s3ClientBuilder.build();
}

@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnClass(value = JsonMapper.class)
static class Jackson2JsonS3ObjectConverterConfiguration {

@ConditionalOnMissingBean
@Bean
S3ObjectConverter s3ObjectConverter(Optional<ObjectMapper> objectMapper) {
return new Jackson2JsonS3ObjectConverter(objectMapper.orElseGet(ObjectMapper::new));
S3ObjectConverter s3ObjectConverter(Optional<JsonMapper> jsonMapper) {
return new Jackson2JsonS3ObjectConverter(jsonMapper.orElseGet(JsonMapper::new));
}
}

@Bean
@ConditionalOnMissingBean
S3OutputStreamProvider inMemoryBufferingS3StreamProvider(S3Client s3Client,
Optional<S3ObjectContentTypeResolver> contentTypeResolver) {
return new InMemoryBufferingS3OutputStreamProvider(s3Client,
contentTypeResolver.orElseGet(PropertiesS3ObjectContentTypeResolver::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package io.awspring.cloud.autoconfigure.sqs;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer;
import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer;
import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails;
Expand All @@ -31,8 +30,12 @@
import io.awspring.cloud.sqs.listener.interceptor.MessageInterceptor;
import io.awspring.cloud.sqs.operations.SqsTemplate;
import io.awspring.cloud.sqs.operations.SqsTemplateBuilder;
import io.awspring.cloud.sqs.support.converter.AbstractMessageConverterFactory;
import io.awspring.cloud.sqs.support.converter.JacksonJsonMessageConverterFactory;
import io.awspring.cloud.sqs.support.converter.MessagingMessageConverter;
import io.awspring.cloud.sqs.support.converter.SqsMessagingMessageConverter;
import io.awspring.cloud.sqs.support.converter.jackson2.LegacyJackson2MessageConverterFactory;
import io.awspring.cloud.sqs.support.converter.jackson2.LegacySqsMessagingMessageConverter;
import io.awspring.cloud.sqs.support.observation.SqsListenerObservation;
import io.awspring.cloud.sqs.support.observation.SqsTemplateObservation;
import io.micrometer.observation.ObservationRegistry;
Expand All @@ -49,6 +52,7 @@
import org.springframework.context.annotation.Import;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.Message;
import tools.jackson.databind.json.JsonMapper;

/**
* {@link EnableAutoConfiguration Auto-configuration} for SQS integration.
Expand Down Expand Up @@ -87,7 +91,8 @@ public SqsAsyncClient sqsAsyncClient(AwsClientBuilderConfigurer awsClientBuilder

@ConditionalOnMissingBean
@Bean
public SqsTemplate sqsTemplate(SqsAsyncClient sqsAsyncClient, ObjectProvider<ObjectMapper> objectMapperProvider,
public SqsTemplate sqsTemplate(SqsAsyncClient sqsAsyncClient,
ObjectProvider<AbstractMessageConverterFactory> objectMapperProvider,
ObjectProvider<ObservationRegistry> observationRegistryProvider,
ObjectProvider<SqsTemplateObservation.Convention> observationConventionProvider,
MessagingMessageConverter<Message> messageConverter) {
Expand All @@ -114,7 +119,8 @@ public SqsMessageListenerContainerFactory<Object> defaultSqsListenerContainerFac
ObjectProvider<AsyncMessageInterceptor<Object>> asyncInterceptors,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<SqsListenerObservation.Convention> observationConventionProvider,
ObjectProvider<MessageInterceptor<Object>> interceptors, ObjectProvider<ObjectMapper> objectMapperProvider,
ObjectProvider<MessageInterceptor<Object>> interceptors,
ObjectProvider<AbstractMessageConverterFactory> objectMapperProvider,
MessagingMessageConverter<?> messagingMessageConverter) {

SqsMessageListenerContainerFactory<Object> factory = new SqsMessageListenerContainerFactory<>();
Expand All @@ -135,18 +141,13 @@ public SqsMessageListenerContainerFactory<Object> defaultSqsListenerContainerFac
return factory;
}

private void setMapperToConverter(MessagingMessageConverter<?> messagingMessageConverter, ObjectMapper om) {
if (messagingMessageConverter instanceof SqsMessagingMessageConverter sqsConverter) {
sqsConverter.setObjectMapper(om);
private void setMapperToConverter(MessagingMessageConverter<?> messagingMessageConverter,
AbstractMessageConverterFactory factory) {
if (messagingMessageConverter instanceof LegacySqsMessagingMessageConverter sqsConverter) {
sqsConverter.setObjectMapper(((LegacyJackson2MessageConverterFactory) factory).getObjectMapper());
}
}

@ConditionalOnMissingBean
@Bean
public MessagingMessageConverter<Message> messageConverter() {
return new SqsMessagingMessageConverter();
}

private void configureProperties(SqsContainerOptionsBuilder options) {
PropertyMapper mapper = PropertyMapper.get();
mapper.from(this.sqsProperties.getQueueNotFoundStrategy()).to(options::queueNotFoundStrategy);
Expand All @@ -157,13 +158,26 @@ private void configureProperties(SqsContainerOptionsBuilder options) {
mapper.from(this.sqsProperties.getListener().getAutoStartup()).to(options::autoStartup);
}

@ConditionalOnMissingBean
@Bean
public MessagingMessageConverter<Message> messageConverter() {
return new SqsMessagingMessageConverter();
}

@Bean
@ConditionalOnMissingBean
public AbstractMessageConverterFactory jsonMapperWrapper(ObjectProvider<JsonMapper> jsonMapper) {
JsonMapper mapper = jsonMapper.getIfAvailable(JsonMapper::new);
return new JacksonJsonMessageConverterFactory(mapper);
}

@Bean
public SqsListenerConfigurer objectMapperCustomizer(ObjectProvider<ObjectMapper> objectMapperProvider) {
ObjectMapper objectMapper = objectMapperProvider.getIfUnique();
public SqsListenerConfigurer objectMapperCustomizer(
ObjectProvider<AbstractMessageConverterFactory> objectProviderWrapper) {
AbstractMessageConverterFactory wrapper = objectProviderWrapper.getIfUnique();
return registrar -> {
// Object Mapper for SqsListener annotations handler method
if (registrar.getObjectMapper() == null && objectMapper != null) {
registrar.setObjectMapper(objectMapper);
if (wrapper != null) {
registrar.setJacksonMapperWrapper(wrapper);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.encryption.s3.S3EncryptionClient;
import tools.jackson.databind.json.JsonMapper;

/**
* Tests for {@link S3AutoConfiguration}.
Expand Down Expand Up @@ -237,7 +238,9 @@ void withJacksonOnClasspathAutoconfiguresObjectConverter() {

@Test
void withoutJacksonOnClasspathDoesNotConfigureObjectConverter() {
contextRunner.withClassLoader(new FilteredClassLoader(ObjectMapper.class, S3EncryptionClient.class))
contextRunner
.withClassLoader(
new FilteredClassLoader(JsonMapper.class, ObjectMapper.class, S3EncryptionClient.class))
.run(context -> {
assertThat(context).doesNotHaveBean(S3ObjectConverter.class);
assertThat(context).doesNotHaveBean(S3Template.class);
Expand All @@ -248,8 +251,7 @@ void withoutJacksonOnClasspathDoesNotConfigureObjectConverter() {
void usesCustomObjectMapperBean() {
contextRunner.withUserConfiguration(CustomJacksonConfiguration.class).run(context -> {
S3ObjectConverter s3ObjectConverter = context.getBean(S3ObjectConverter.class);
assertThat(s3ObjectConverter).extracting("objectMapper")
.isEqualTo(context.getBean("customObjectMapper"));
assertThat(s3ObjectConverter).extracting("jsonMapper").isEqualTo(context.getBean("customJsonMapper"));
});
}

Expand Down Expand Up @@ -347,8 +349,8 @@ void setsRegionToDefault() {
@Configuration(proxyBeanMethods = false)
static class CustomJacksonConfiguration {
@Bean
ObjectMapper customObjectMapper() {
return new ObjectMapper();
JsonMapper customJsonMapper() {
return new JsonMapper();
}
}

Expand Down
Loading