diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc
index a6a2e0356..fbe8c4024 100644
--- a/docs/src/main/asciidoc/_configprops.adoc
+++ b/docs/src/main/asciidoc/_configprops.adoc
@@ -24,6 +24,7 @@
|cloud.aws.loader.queue-capacity | | The maximum queue capacity for backed up S3 requests. @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setQueueCapacity(int)
|cloud.aws.region.auto | true | Enables automatic region detection based on the EC2 meta data service.
|cloud.aws.region.static | |
+|cloud.aws.region.use-default-aws-region-chain | false | Whether default AWS SDK region provider chain should be used when auto is set to true.
|cloud.aws.stack.auto | true | Enables the automatic stack name detection for the application.
|cloud.aws.stack.name | myStackName | The name of the manually configured stack name that will be used to retrieve the resources.
diff --git a/docs/src/main/asciidoc/spring-cloud-aws.adoc b/docs/src/main/asciidoc/spring-cloud-aws.adoc
index cb6e0c66a..b2deccfe9 100644
--- a/docs/src/main/asciidoc/spring-cloud-aws.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-aws.adoc
@@ -87,6 +87,26 @@ For milestones:
----
+=== Amazon SDK dependency version management
+
+Amazon SDK is released more frequently than Spring Cloud AWS. If you need to use newer version of AWS SDK than one configured by Spring Cloud AWS
+add AWS SDK BOM to dependency management section making sure it is declared before any other BOM dependency that configures AWS SDK dependencies.
+
+[source,xml,indent=0]
+----
+
+
+
+ com.amazonaws
+ aws-java-sdk-bom
+ ${aws-java-sdk.version}
+ pom
+ import
+
+
+
+----
+
=== Amazon SDK configuration
The Spring Cloud AWS configuration is currently done using custom elements provided by Spring Cloud AWS namespaces.
JavaConfig will be supported soon. The configuration setup is done directly in Spring XML configuration files
@@ -108,6 +128,18 @@ use of the modules. A typical XML configuration to use Spring Cloud AWS is outli
----
+[TIP]
+====
+On application startup, for its internal purposes Spring Cloud AWS performs a check if application runs in AWS cloud environment
+by using `EC2MetadataUtils` class provided by AWS SDK. Starting from version 1.11.678, AWS SDK logs a warning message with exception when this check is made outside of AWS environment.
+This warning message can be hidden by setting `ERROR` logging level on `com.amazonaws.util.EC2MetadataUtils` class.
+
+[source,indent=0]
+----
+logging.level.com.amazonaws.util.EC2MetadataUtils=error
+----
+====
+
==== SDK credentials configuration
In order to make calls to the Amazon Web Service the credentials must be configured for the the Amazon SDK. Spring Cloud AWS
provides support to configure an application context specific credentials that are used for _each_ service call for requests done
@@ -275,25 +307,40 @@ Spring Boot provides a standard way to define properties with property file or Y
AWS provides support to configure the credential information with the Spring Boot application configuration files.
Spring Cloud AWS provides the following properties to configure the credentials setup for the whole application.
+Unless `cloud.aws.credentials.use-default-aws-credentials-chain` is set to `true`, Spring Cloud AWS configures following
+credentials chain:
+
+1. `AWSStaticCredentialsProvider` if `cloud.aws.credentials.access-key` is provided
+2. `EC2ContainerCredentialsProviderWrapper` unless `cloud.aws.credentials.instance-profile` is set to `false`
+3. `ProfileCredentialsProvider`
+
[cols="3*", options="header"]
|===
|property
|example
|description
-|cloud.aws.credentials.accessKey
+|cloud.aws.credentials.access-key
|AKIAIOSFODNN7EXAMPLE
|The access key to be used with a static provider
-|cloud.aws.credentials.secretKey
+|cloud.aws.credentials.secret-key
|wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|The secret key to be used with a static provider
-|cloud.aws.credentials.instanceProfile
+|cloud.aws.credentials.instance-profile
|true
|Configures an instance profile credentials provider with no further configuration
-|cloud.aws.credentials.useDefaultAwsCredentialsChain
+|cloud.aws.credentials.profile-name
+|default
+|The name of a configuration profile in the specified configuration file
+
+|cloud.aws.credentials.profile-path
+|`~/.aws/credentials`
+|The file path where the profile configuration file is located. Defaults to `~/.aws/credentials` if value is not provided
+
+|cloud.aws.credentials.use-default-aws-credentials-chain
|true
|Use the DefaultAWSCredentials Chain instead of configuring a custom credentials chain
|===
@@ -315,6 +362,10 @@ The properties to configure the region are shown below
|true
|Enables automatic region detection based on the EC2 meta data service
+|cloud.aws.region.use-default-aws-region-chain
+|true
+|Use the DefaultAWSRegion Chain instead of configuring a custom region chain
+
|cloud.aws.region.static
|eu-west-1
|Configures a static region for the application. Possible regions are (currently) us-east-1, us-west-1, us-west-2,
@@ -660,6 +711,17 @@ You can configure the following settings in a Spring Cloud `bootstrap.properties
|Can be used to disable the Parameter Store Configuration support even though the auto-configuration is on the classpath.
|===
+[TIP]
+====
+In order to find out which properties are retrieved from AWS Parameter Store on application startup,
+turn on `DEBUG` logging on `org.springframework.cloud.aws.paramstore.AwsParamStorePropertySource` class.
+
+[source,indent=0]
+----
+logging.level.org.springframework.cloud.aws.paramstore.AwsParamStorePropertySource=debug
+----
+====
+
=== Integrating your Spring Cloud application with the AWS Secrets Manager
Spring Cloud provides support for centralized configuration, which can be read and made available as a regular Spring
@@ -2272,6 +2334,41 @@ public class SimpleResourceLoadingBean {
}
}
----
+== CloudWatch Metrics
+Spring Cloud AWS provides Spring Boot auto-configuration for Micrometer CloudWatch integration.
+To send metrics to CloudWatch add a dependency to `spring-cloud-aws-actuator` module:
+
+[source,xml,indent=0]
+----
+
+ org.springframework.cloud
+ spring-cloud-aws-actuator
+
+----
+
+Additionally CloudWatch integration requires a value provided for `management.metrics.export.cloudwatch.namespace` configuration property.
+
+Following configuration properties are available to configure CloudWatch integration:
+
+[cols="3*", options="header"]
+|===
+|property
+|default
+|description
+
+|management.metrics.export.cloudwatch.namespace
+|
+|The namespace which will be used when sending metrics to CloudWatch. This property is needed and must not be null.
+
+|management.metrics.export.cloudwatch.enabled
+|true
+|If CloudWatch integration should be enabled. This property should be likely set to `false` for a local development profile.
+
+|management.metrics.export.cloudwatch.step
+|1m
+|The interval at which metrics are sent to CloudWatch. The default is 1 minute.
+|===
+
== Configuration properties
To see the list of all Spring Cloud AWS related configuration properties please check link:appendix.html[the Appendix page].
diff --git a/pom.xml b/pom.xml
index 3724cc666..94f767b64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -114,6 +114,11 @@
spring-boot-starter-test
test
+
+ junit
+ junit
+ test
+
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfiguration.java
index 524bc0b9d..56fd92d6a 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfiguration.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfiguration.java
@@ -82,9 +82,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
return;
}
- Boolean useDefaultCredentialsChain = this.environment.getProperty(
- AWS_CREDENTIALS_PROPERTY_PREFIX + ".use-default-aws-credentials-chain",
- Boolean.class, false);
+ Boolean useDefaultCredentialsChain = this.environment
+ .getProperty(
+ AWS_CREDENTIALS_PROPERTY_PREFIX
+ + ".use-default-aws-credentials-chain",
+ Boolean.class, false);
String accessKey = this.environment
.getProperty(AWS_CREDENTIALS_PROPERTY_PREFIX + ".access-key");
String secretKey = this.environment
@@ -99,7 +101,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
AWS_CREDENTIALS_PROPERTY_PREFIX + ".instance-profile",
Boolean.class, true)
&& !this.environment.containsProperty(
- AWS_CREDENTIALS_PROPERTY_PREFIX + ".access-key"),
+ AWS_CREDENTIALS_PROPERTY_PREFIX + ".access-key"),
this.environment.getProperty(
AWS_CREDENTIALS_PROPERTY_PREFIX + ".profile-name",
DEFAULT_PROFILE_NAME),
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfiguration.java
index c7521b847..ac2e8b686 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfiguration.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfiguration.java
@@ -70,12 +70,19 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
return;
}
- registerRegionProvider(registry, this.environment.getProperty(
+ boolean useDefaultRegionChain = this.environment.getProperty(
+ AWS_REGION_PROPERTIES_PREFIX + ".use-default-aws-region-chain",
+ Boolean.class, false);
+
+ String staticRegion = this.environment
+ .getProperty(AWS_REGION_PROPERTIES_PREFIX + ".static");
+
+ boolean autoDetect = this.environment.getProperty(
AWS_REGION_PROPERTIES_PREFIX + ".auto", Boolean.class, true)
- && !StringUtils.hasText(this.environment
- .getProperty(AWS_REGION_PROPERTIES_PREFIX + ".static")),
- this.environment
- .getProperty(AWS_REGION_PROPERTIES_PREFIX + ".static"));
+ && !StringUtils.hasText(staticRegion);
+
+ registerRegionProvider(registry, autoDetect, useDefaultRegionChain,
+ staticRegion);
}
@Override
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/properties/AwsRegionProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/properties/AwsRegionProperties.java
index 4e4a23496..155c540af 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/properties/AwsRegionProperties.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/org/springframework/cloud/aws/autoconfigure/context/properties/AwsRegionProperties.java
@@ -20,6 +20,7 @@
* Properties related to AWS region configuration.
*
* @author Tom Gianos
+ * @author Maciej Walkowiak
* @since 2.0.2
* @see org.springframework.cloud.aws.autoconfigure.context.ContextRegionProviderAutoConfiguration
*/
@@ -30,6 +31,12 @@ public class AwsRegionProperties {
*/
private boolean auto = true;
+ /**
+ * Whether default AWS SDK region provider chain should be used when auto is set to
+ * true.
+ */
+ private boolean useDefaultAwsRegionChain;
+
/**
* Configures a static region for the application. Possible regions are (currently)
* us-east-1, us-west-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-1,
@@ -60,4 +67,12 @@ public void setStatic(String staticRegion) {
this.staticRegion = staticRegion;
}
+ public boolean isUseDefaultAwsRegionChain() {
+ return useDefaultAwsRegionChain;
+ }
+
+ public void setUseDefaultAwsRegionChain(boolean useDefaultAwsRegionChain) {
+ this.useDefaultAwsRegionChain = useDefaultAwsRegionChain;
+ }
+
}
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfigurationTest.java
index 24fe26e9b..3f4ec3e35 100644
--- a/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfigurationTest.java
+++ b/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextCredentialsAutoConfigurationTest.java
@@ -148,7 +148,8 @@ public void credentialsProvider_propertyToUseDefaultIsSet_configuresDefaultAwsCr
public void credentialsProvider_dashSeparatedPropertyToUseDefaultIsSet_configuresDefaultAwsCredentialsProvider() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(ContextCredentialsAutoConfiguration.class);
- TestPropertyValues.of("cloud.aws.credentials.use-default-aws-credentials-chain:true")
+ TestPropertyValues
+ .of("cloud.aws.credentials.use-default-aws-credentials-chain:true")
.applyTo(this.context);
this.context.refresh();
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfigurationTest.java
index acfd35b43..745116481 100644
--- a/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfigurationTest.java
+++ b/spring-cloud-aws-autoconfigure/src/test/java/org/springframework/cloud/aws/autoconfigure/context/ContextRegionProviderAutoConfigurationTest.java
@@ -21,16 +21,20 @@
import org.junit.After;
import org.junit.Test;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.test.util.TestPropertyValues;
+import org.springframework.cloud.aws.core.region.DefaultAwsRegionProviderChainDelegate;
import org.springframework.cloud.aws.core.region.Ec2MetadataRegionProvider;
import org.springframework.cloud.aws.core.region.StaticRegionProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Agim Emruli
* @author Petromir Dzhunev
+ * @author Maciej Walkowiak
*/
public class ContextRegionProviderAutoConfigurationTest {
@@ -93,4 +97,23 @@ public void regionProvider_staticRegionConfigured_staticRegionProviderWithConfig
.isEqualTo(Region.getRegion(Regions.EU_WEST_1));
}
+ @Test
+ public void regionProvider_autoDetectionAndDefaultChainConfigured_DefaultAwsRegionProviderChainDelegateConfigured() {
+ // Arrange
+ this.context = new AnnotationConfigApplicationContext();
+ this.context.register(ContextRegionProviderAutoConfiguration.class);
+ TestPropertyValues.of("cloud.aws.region.auto:true").applyTo(this.context);
+ TestPropertyValues.of("cloud.aws.region.useDefaultAwsRegionChain:true")
+ .applyTo(this.context);
+
+ // Act
+ this.context.refresh();
+
+ // Assert
+ assertThat(this.context.getBean(DefaultAwsRegionProviderChainDelegate.class))
+ .isNotNull();
+ assertThatThrownBy(() -> this.context.getBean(Ec2MetadataRegionProvider.class))
+ .isInstanceOf(NoSuchBeanDefinitionException.class);
+ }
+
}
diff --git a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrar.java b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrar.java
index 09e0b4182..7f9cb4591 100644
--- a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrar.java
+++ b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrar.java
@@ -43,9 +43,12 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
+ importingClassMetadata.getClassName());
boolean autoDetect = annotationAttributes.getBoolean("autoDetect");
+ boolean useDefaultAwsRegionChain = annotationAttributes
+ .getBoolean("useDefaultAwsRegionChain");
String configuredRegion = annotationAttributes.getString("region");
- registerRegionProvider(registry, autoDetect, configuredRegion);
+ registerRegionProvider(registry, autoDetect, useDefaultAwsRegionChain,
+ configuredRegion);
}
}
diff --git a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/EnableContextRegion.java b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/EnableContextRegion.java
index 63eb5edf5..563c4a29b 100644
--- a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/EnableContextRegion.java
+++ b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/annotation/EnableContextRegion.java
@@ -21,6 +21,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import com.amazonaws.regions.AwsRegionProviderChain;
+
import org.springframework.context.annotation.Import;
/**
@@ -29,11 +31,15 @@
* Service clients that are created inside the application context (by the Spring Cloud
* AWS classes). A region can be either manually configured
* {@link EnableContextRegion#region()} with a constant expression, dynamic expression
- * (using a SpEL expression) or a place holder. The region can also be dynamically
- * retrieved from the EC2 instance meta-data if the application context is running inside
- * a EC2 instance by enabling the {@link EnableContextRegion#autoDetect()} attribute.
+ * (using a SpEL expression) or a place holder. If the application context is running
+ * inside a EC2 instance The region can also be dynamically retrieved from the EC2
+ * instance meta-data by enabling the {@link EnableContextRegion#autoDetect()} attribute
+ * or from the default AWS SDK {@link AwsRegionProviderChain} by enabling
+ * {@link EnableContextRegion#autoDetect()} and
+ * {@link EnableContextRegion#useDefaultAwsRegionChain()}.
*
* @author Agim Emruli
+ * @author Maciej Walkowiak
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@@ -58,4 +64,12 @@
*/
boolean autoDetect() default false;
+ /**
+ * Whether default AWS SDK region provider chain should be used when auto is set to
+ * true.
+ * @return - if default AWS SDK region provider chain should be used for region
+ * resolution.
+ */
+ boolean useDefaultAwsRegionChain() default false;
+
}
diff --git a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/support/ContextConfigurationUtils.java b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/support/ContextConfigurationUtils.java
index 144bafffb..461dd9e4e 100644
--- a/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/support/ContextConfigurationUtils.java
+++ b/spring-cloud-aws-context/src/main/java/org/springframework/cloud/aws/context/config/support/ContextConfigurationUtils.java
@@ -31,6 +31,7 @@
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.cloud.aws.core.config.AmazonWebserviceClientConfigurationUtils;
import org.springframework.cloud.aws.core.credentials.CredentialsProviderFactoryBean;
+import org.springframework.cloud.aws.core.region.DefaultAwsRegionProviderChainDelegate;
import org.springframework.cloud.aws.core.region.Ec2MetadataRegionProvider;
import org.springframework.cloud.aws.core.region.StaticRegionProvider;
import org.springframework.util.StringUtils;
@@ -55,7 +56,7 @@ private ContextConfigurationUtils() {
}
public static void registerRegionProvider(BeanDefinitionRegistry registry,
- boolean autoDetect, String configuredRegion) {
+ boolean autoDetect, boolean useDefaultRegionChain, String configuredRegion) {
if (autoDetect && StringUtils.hasText(configuredRegion)) {
throw new IllegalArgumentException(
"No region must be configured if autoDetect is defined as true");
@@ -64,8 +65,9 @@ public static void registerRegionProvider(BeanDefinitionRegistry registry,
AbstractBeanDefinition beanDefinition;
if (autoDetect) {
- beanDefinition = BeanDefinitionBuilder
- .genericBeanDefinition(Ec2MetadataRegionProvider.class)
+ beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(
+ useDefaultRegionChain ? DefaultAwsRegionProviderChainDelegate.class
+ : Ec2MetadataRegionProvider.class)
.getBeanDefinition();
}
else if (StringUtils.hasText(configuredRegion)) {
diff --git a/spring-cloud-aws-context/src/test/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrarTest.java b/spring-cloud-aws-context/src/test/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrarTest.java
index b24b7b0f8..51f8efbfa 100644
--- a/spring-cloud-aws-context/src/test/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrarTest.java
+++ b/spring-cloud-aws-context/src/test/java/org/springframework/cloud/aws/context/config/annotation/ContextRegionConfigurationRegistrarTest.java
@@ -82,6 +82,21 @@ public void regionProvider_withAutoDetectedRegion_dynamicRegionProviderConfigure
assertThat(staticRegionProvider).isNotNull();
}
+ @Test
+ public void regionProvider_withAutoDetectedRegionAndDefaultChain_defaulAwsChainRegionProviderConfigured()
+ throws Exception {
+ // Arrange
+ this.context = new AnnotationConfigApplicationContext(
+ ApplicationConfigurationWithDynamicRegionProvider.class);
+
+ // Act
+ Ec2MetadataRegionProvider staticRegionProvider = this.context
+ .getBean(Ec2MetadataRegionProvider.class);
+
+ // Assert
+ assertThat(staticRegionProvider).isNotNull();
+ }
+
@Test
public void regionProvider_withExpressionConfiguredRegion_staticRegionProviderConfigured()
throws Exception {
@@ -219,4 +234,10 @@ static class ApplicationConfigurationWithWrongRegion {
}
+ @Configuration(proxyBeanMethods = false)
+ @EnableContextRegion(autoDetect = true, useDefaultAwsRegionChain = true)
+ static class ApplicationConfigurationWithAutoDetectionAndDefaultChain {
+
+ }
+
}
diff --git a/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResource.java b/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResource.java
index 0c58764a1..aeffbd19d 100644
--- a/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResource.java
+++ b/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResource.java
@@ -23,7 +23,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -139,8 +143,19 @@ public String getFilename() throws IllegalStateException {
@Override
public URL getURL() throws IOException {
Region region = this.amazonS3.getRegion().toAWSRegion();
+ String encodedObjectName = URLEncoder.encode(this.objectName,
+ StandardCharsets.UTF_8.toString());
return new URL("https", region.getServiceEndpoint(AmazonS3Client.S3_SERVICE_NAME),
- "/" + this.bucketName + "/" + this.objectName);
+ "/" + this.bucketName + "/" + encodedObjectName);
+ }
+
+ public URI getS3Uri() {
+ try {
+ return new URI("s3", "//" + this.bucketName + "/" + this.objectName, null);
+ }
+ catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to resolve s3:// uri", e);
+ }
}
@Override
diff --git a/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/region/DefaultAwsRegionProviderChainDelegate.java b/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/region/DefaultAwsRegionProviderChainDelegate.java
new file mode 100644
index 000000000..0d7052ca7
--- /dev/null
+++ b/spring-cloud-aws-core/src/main/java/org/springframework/cloud/aws/core/region/DefaultAwsRegionProviderChainDelegate.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.aws.core.region;
+
+import com.amazonaws.regions.DefaultAwsRegionProviderChain;
+import com.amazonaws.regions.Region;
+import com.amazonaws.regions.RegionUtils;
+
+/**
+ * {@link RegionProvider} implementation that delegates to
+ * {@link DefaultAwsRegionProviderChain} enabling loading region configuration from
+ * environment variables, system properties, AWS profile, and instance metadata.
+ *
+ * @author Maciej Walkowiak
+ * @since 1.0
+ */
+public class DefaultAwsRegionProviderChainDelegate implements RegionProvider {
+
+ private final DefaultAwsRegionProviderChain delegate = new DefaultAwsRegionProviderChain();
+
+ @Override
+ public Region getRegion() {
+ return RegionUtils.getRegion(delegate.getRegion());
+ }
+
+}
diff --git a/spring-cloud-aws-core/src/test/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResourceTest.java b/spring-cloud-aws-core/src/test/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResourceTest.java
index 6549927c5..1363f07be 100644
--- a/spring-cloud-aws-core/src/test/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResourceTest.java
+++ b/spring-cloud-aws-core/src/test/java/org/springframework/cloud/aws/core/io/s3/SimpleStorageResourceTest.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URI;
import java.net.URL;
import java.util.Date;
@@ -231,6 +232,21 @@ public void getUrl_existingObject_returnsUrlWithS3Prefix() throws Exception {
}
+ @Test
+ public void getUrl_existingObject_returnsUrlWithS3Scheme() throws Exception {
+
+ AmazonS3Client amazonS3 = mock(AmazonS3Client.class);
+
+ // Act
+ SimpleStorageResource simpleStorageResource = new SimpleStorageResource(amazonS3,
+ "bucket", "object", new SyncTaskExecutor());
+
+ // Assert
+ assertThat(simpleStorageResource.getS3Uri())
+ .isEqualTo(new URI("s3://bucket/object"));
+
+ }
+
@Test
public void getFile_existingObject_throwsMeaningFullException() throws Exception {
@@ -297,4 +313,15 @@ public void writeFile_forNewFile_writesFileContent() throws Exception {
// Assert
}
+ @Test
+ public void getUri_encodes_objectName() throws Exception {
+ AmazonS3 s3 = mock(AmazonS3.class);
+ when(s3.getRegion()).thenReturn(Region.US_West_2);
+ SimpleStorageResource resource = new SimpleStorageResource(s3, "bucketName",
+ "some/[objectName]", new SyncTaskExecutor());
+
+ assertThat(resource.getURI()).isEqualTo(new URI(
+ "https://s3.us-west-2.amazonaws.com/bucketName/some%2F%5BobjectName%5D"));
+ }
+
}
diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml
index f90132fdd..818948ec3 100644
--- a/spring-cloud-aws-dependencies/pom.xml
+++ b/spring-cloud-aws-dependencies/pom.xml
@@ -31,7 +31,7 @@
Spring Cloud AWS Dependencies
Spring Cloud AWS Dependencies
- 1.11.415
+ 1.11.792
1.1.1
1.0.0
diff --git a/spring-cloud-aws-integration-test/src/test/java/org/springframework/cloud/aws/messaging/AbstractContainerTest.java b/spring-cloud-aws-integration-test/src/test/java/org/springframework/cloud/aws/messaging/AbstractContainerTest.java
index a5025fa88..c8eb53594 100644
--- a/spring-cloud-aws-integration-test/src/test/java/org/springframework/cloud/aws/messaging/AbstractContainerTest.java
+++ b/spring-cloud-aws-integration-test/src/test/java/org/springframework/cloud/aws/messaging/AbstractContainerTest.java
@@ -47,7 +47,7 @@ public void tearDown() throws Exception {
this.simpleMessageListenerContainer.stop(countDownLatch::countDown);
if (!countDownLatch.await(15, TimeUnit.SECONDS)) {
- throw new Exception("Couldn't stop container withing 15 seconds");
+ throw new Exception("Couldn't stop container within 15 seconds");
}
}
}
diff --git a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/config/annotation/SnsWebConfiguration.java b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/config/annotation/SnsWebConfiguration.java
index 99d7ee4f5..0e0ea3883 100644
--- a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/config/annotation/SnsWebConfiguration.java
+++ b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/config/annotation/SnsWebConfiguration.java
@@ -46,4 +46,5 @@ public void addArgumentResolvers(
}
};
}
+
}
diff --git a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandler.java b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandler.java
index 168d1c9a8..f4cf151c5 100644
--- a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandler.java
+++ b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandler.java
@@ -52,6 +52,7 @@
import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.util.ClassUtils;
import org.springframework.util.comparator.ComparableComparator;
import org.springframework.validation.Errors;
@@ -233,7 +234,15 @@ protected void handleNoMatch(Set ts, String lookupDestinatio
@Override
protected void processHandlerMethodException(HandlerMethod handlerMethod,
Exception ex, Message> message) {
- super.processHandlerMethodException(handlerMethod, ex, message);
+ InvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(
+ handlerMethod, ex);
+ if (exceptionHandlerMethod != null) {
+ super.processHandlerMethodException(handlerMethod, ex, message);
+ }
+ else {
+ this.logger.error("An exception occurred while invoking the handler method",
+ ex);
+ }
throw new MessagingException(
"An exception occurred while invoking the handler method", ex);
}
diff --git a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/SimpleMessageListenerContainer.java b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/SimpleMessageListenerContainer.java
index 48765ad7b..cb55be6d5 100644
--- a/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/SimpleMessageListenerContainer.java
+++ b/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/SimpleMessageListenerContainer.java
@@ -418,7 +418,7 @@ public void run() {
applyDeletionPolicyOnSuccess(receiptHandle);
}
catch (MessagingException messagingException) {
- applyDeletionPolicyOnError(receiptHandle, messagingException);
+ applyDeletionPolicyOnError(receiptHandle);
}
}
@@ -430,17 +430,12 @@ private void applyDeletionPolicyOnSuccess(String receiptHandle) {
}
}
- private void applyDeletionPolicyOnError(String receiptHandle,
- MessagingException messagingException) {
+ private void applyDeletionPolicyOnError(String receiptHandle) {
if (this.deletionPolicy == SqsMessageDeletionPolicy.ALWAYS
|| (this.deletionPolicy == SqsMessageDeletionPolicy.NO_REDRIVE
&& !this.hasRedrivePolicy)) {
deleteMessage(receiptHandle);
}
- else if (this.deletionPolicy == SqsMessageDeletionPolicy.ON_SUCCESS) {
- getLogger().error("Exception encountered while processing message.",
- messagingException);
- }
}
private void deleteMessage(String receiptHandle) {
diff --git a/spring-cloud-aws-messaging/src/test/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandlerTest.java b/spring-cloud-aws-messaging/src/test/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandlerTest.java
index 61f28165e..6f55b1aef 100644
--- a/spring-cloud-aws-messaging/src/test/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandlerTest.java
+++ b/spring-cloud-aws-messaging/src/test/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandlerTest.java
@@ -70,6 +70,7 @@
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -566,6 +567,40 @@ public void getMappingForMethod_methodWithExpressionProducingMultipleQueueNames_
.containsAll(Arrays.asList("queueOne", "queueTwo"))).isTrue();
}
+ @Test
+ public void processHandlerMethodException_invocableHandlerMethodNotAvailable_errorMustBeLogged() {
+ // Arrange
+ StaticApplicationContext applicationContext = new StaticApplicationContext();
+ applicationContext.registerSingleton("sqsListenerWithoutMessageExceptionHandler",
+ SqsListenerWithoutMessageExceptionHandler.class);
+ applicationContext.registerBeanDefinition("queueMessageHandler",
+ getQueueMessageHandlerBeanDefinition());
+ applicationContext.refresh();
+ MessageHandler messageHandler = applicationContext.getBean(MessageHandler.class);
+
+ LoggerContext logContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+ ListAppender appender = new ListAppender<>();
+ appender.start();
+ Logger log = logContext.getLogger(QueueMessageHandler.class);
+ log.setLevel(Level.ERROR);
+ log.addAppender(appender);
+ appender.setContext(log.getLoggerContext());
+
+ // Act
+ try {
+ messageHandler.handleMessage(MessageBuilder.withPayload("testContent")
+ .setHeader(QueueMessageHandler.LOGICAL_RESOURCE_ID, "receive")
+ .build());
+ fail();
+ }
+ catch (MessagingException e) {
+ // ignore
+ }
+
+ // Assert
+ assertThat(appender.list).hasSize(1);
+ }
+
@SuppressWarnings("UnusedDeclaration")
private static class IncomingMessageHandler {
@@ -604,6 +639,15 @@ private String getLastReceivedMessage() {
}
+ private static class SqsListenerWithoutMessageExceptionHandler {
+
+ @SqsListener("receive")
+ public String receive(String value) {
+ throw new RuntimeException("test exception");
+ }
+
+ }
+
private static class IncomingMessageHandlerWithMultipleQueueNames {
private String lastReceivedMessage;
diff --git a/spring-cloud-aws-parameter-store-config/src/main/java/org/springframework/cloud/aws/paramstore/AwsParamStorePropertySource.java b/spring-cloud-aws-parameter-store-config/src/main/java/org/springframework/cloud/aws/paramstore/AwsParamStorePropertySource.java
index f63784884..68610c730 100644
--- a/spring-cloud-aws-parameter-store-config/src/main/java/org/springframework/cloud/aws/paramstore/AwsParamStorePropertySource.java
+++ b/spring-cloud-aws-parameter-store-config/src/main/java/org/springframework/cloud/aws/paramstore/AwsParamStorePropertySource.java
@@ -24,6 +24,8 @@
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathRequest;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathResult;
import com.amazonaws.services.simplesystemsmanagement.model.Parameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.core.env.EnumerablePropertySource;
@@ -37,6 +39,9 @@
public class AwsParamStorePropertySource
extends EnumerablePropertySource {
+ private static final Logger LOGGER = LoggerFactory
+ .getLogger(AwsParamStorePropertySource.class);
+
private String context;
private Map properties = new LinkedHashMap<>();
@@ -69,6 +74,8 @@ private void getParameters(GetParametersByPathRequest paramsRequest) {
.getParametersByPath(paramsRequest);
for (Parameter parameter : paramsResult.getParameters()) {
String key = parameter.getName().replace(context, "").replace('/', '.');
+ LOGGER.debug("Populating property retrieved from AWS Parameter Store: {}",
+ key);
properties.put(key, parameter.getValue());
}
if (paramsResult.getNextToken() != null) {
diff --git a/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java
index b59da682a..aea23d3c2 100644
--- a/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java
+++ b/spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java
@@ -43,6 +43,8 @@
*/
public class AwsSecretsManagerPropertySourceLocator implements PropertySourceLocator {
+ private String propertySourceName;
+
private AWSSecretsManager smClient;
private AwsSecretsManagerProperties properties;
@@ -51,12 +53,16 @@ public class AwsSecretsManagerPropertySourceLocator implements PropertySourceLoc
private Log logger = LogFactory.getLog(getClass());
- public AwsSecretsManagerPropertySourceLocator(AWSSecretsManager smClient,
- AwsSecretsManagerProperties properties) {
+ public AwsSecretsManagerPropertySourceLocator(String propertySourceName, AWSSecretsManager smClient, AwsSecretsManagerProperties properties) {
+ this.propertySourceName = propertySourceName;
this.smClient = smClient;
this.properties = properties;
}
+ public AwsSecretsManagerPropertySourceLocator(AWSSecretsManager smClient, AwsSecretsManagerProperties properties) {
+ this("aws-secrets-manager", smClient, properties);
+ }
+
public List getContexts() {
return contexts;
}
@@ -89,8 +95,7 @@ public PropertySource> locate(Environment environment) {
Collections.reverse(this.contexts);
- CompositePropertySource composite = new CompositePropertySource(
- "aws-secrets-manager");
+ CompositePropertySource composite = new CompositePropertySource(this.propertySourceName);
for (String propertySourceContext : this.contexts) {
try {
diff --git a/spring-cloud-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocatorTest.java b/spring-cloud-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocatorTest.java
new file mode 100644
index 000000000..d487fb32a
--- /dev/null
+++ b/spring-cloud-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocatorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.aws.secretsmanager;
+
+import com.amazonaws.services.secretsmanager.AWSSecretsManager;
+import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
+import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
+import org.junit.Test;
+
+import org.springframework.core.env.PropertySource;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit test for {@link AwsSecretsManagerPropertySourceLocator}.
+ *
+ * @author Anthony Foulfoin
+ */
+public class AwsSecretsManagerPropertySourceLocatorTest {
+
+ private AWSSecretsManager smClient = mock(AWSSecretsManager.class);
+
+ private MockEnvironment env = new MockEnvironment();
+
+ @Test
+ public void locate_nameSpecifiedInConstructor_returnsPropertySourceWithSpecifiedName() {
+ GetSecretValueResult secretValueResult = new GetSecretValueResult();
+ secretValueResult.setSecretString("{\"key1\": \"value1\", \"key2\": \"value2\"}");
+ when(smClient.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);
+
+ AwsSecretsManagerProperties properties = new AwsSecretsManagerProperties();
+ AwsSecretsManagerPropertySourceLocator locator = new AwsSecretsManagerPropertySourceLocator("my-name", smClient, properties);
+
+ PropertySource propertySource = locator.locate(env);
+
+ assertThat(propertySource.getName()).isEqualTo("my-name");
+ }
+
+ @Test
+ public void locate_nameNotSpecifiedInConstructor_returnsPropertySourceWithDefaultName() {
+ GetSecretValueResult secretValueResult = new GetSecretValueResult();
+ secretValueResult.setSecretString("{\"key1\": \"value1\", \"key2\": \"value2\"}");
+ when(smClient.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);
+
+ AwsSecretsManagerProperties properties = new AwsSecretsManagerProperties();
+ AwsSecretsManagerPropertySourceLocator locator = new AwsSecretsManagerPropertySourceLocator(smClient, properties);
+
+ PropertySource propertySource = locator.locate(env);
+
+ assertThat(propertySource.getName()).isEqualTo("aws-secrets-manager");
+ }
+
+}