Skip to content

Commit f04b647

Browse files
committed
Add S3PropertiesLocation annotation
1 parent f70260d commit f04b647

File tree

9 files changed

+170
-44
lines changed

9 files changed

+170
-44
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Declare a spring bean `S3PropertyPlaceholderConfigurer` using yours AWS credenci
3333
@Bean
3434
S3PropertyPlaceholderConfigurer s3PropertyPlaceholderConfigurer(AmazonS3 s3) {
3535
S3PropertyPlaceholderConfigurer s3PropertyPlaceholderConfigurer = new S3PropertyPlaceholderConfigurer(s3);
36-
s3PropertyPlaceholderConfigurer.setS3Locations(new String[]{"s3://my-bucket/my-folder/my-properties.properties"});
36+
s3PropertyPlaceholderConfigurer.setS3Locations("s3://my-bucket/my-folder/my-properties.properties");
3737

3838
return s3PropertyPlaceholderConfigurer;
3939
}

build.gradle

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ repositories {
4646
}
4747

4848
dependencies {
49-
compile "org.springframework:spring-core:4.3.4.RELEASE"
50-
compile "org.springframework:spring-beans:4.3.4.RELEASE"
51-
compile "org.springframework.cloud:spring-cloud-aws-autoconfigure:1.1.3.RELEASE"
52-
49+
compile "org.springframework:spring-context:4.3.4.RELEASE"
50+
compile "org.springframework.cloud:spring-cloud-aws-core:1.1.3.RELEASE"
51+
5352
testCompile "junit:junit:4.12"
5453
testCompile "org.mockito:mockito-core:1.10.19"
5554
testCompile "org.assertj:assertj-core:1.0.0"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.spring.loader;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
import org.springframework.context.annotation.Import;
10+
11+
/**
12+
* Allow the auto configuration of the {@link S3PropertyPlaceholderConfigurer} bean.
13+
*
14+
* @author Eric Dallo
15+
* @since 1.0.3
16+
* @see S3PropertiesLocationConfiguration
17+
*/
18+
@Target(ElementType.TYPE)
19+
@Retention(RetentionPolicy.RUNTIME)
20+
@Import({S3ResourseLoaderConfiguration.class, S3PropertiesLocationConfiguration.class})
21+
@Documented
22+
public @interface S3PropertiesLocation {
23+
24+
/**
25+
* The location of the properties in aws s3.
26+
*
27+
* @return the path of aws s3 properties, e.g. my-bucket/my-folder/app.properties
28+
*/
29+
String[] value();
30+
31+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.spring.loader;
2+
3+
import org.springframework.beans.MutablePropertyValues;
4+
import org.springframework.beans.factory.config.BeanDefinition;
5+
import org.springframework.beans.factory.config.RuntimeBeanReference;
6+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
7+
import org.springframework.beans.factory.support.RootBeanDefinition;
8+
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
9+
import org.springframework.core.annotation.AnnotationAttributes;
10+
import org.springframework.core.type.AnnotationMetadata;
11+
12+
/**
13+
* Creates the {@link S3PropertyPlaceholderConfigurer} bean.
14+
* For use with the {@link S3PropertiesLocation} annotation.
15+
*
16+
* @author Eric Dallo
17+
* @since 1.0.3
18+
* @see S3PropertiesLocation
19+
* @see S3PropertyPlaceholderConfigurer
20+
*/
21+
class S3PropertiesLocationConfiguration implements ImportBeanDefinitionRegistrar {
22+
23+
@Override
24+
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
25+
BeanDefinition bd = new RootBeanDefinition(S3PropertyPlaceholderConfigurer.class);
26+
27+
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(S3PropertiesLocation.class.getName()));
28+
String[] locations = attributes.getStringArray("value");
29+
30+
MutablePropertyValues properties = bd.getPropertyValues();
31+
32+
properties.addPropertyValue("s3ResourceLoader", new RuntimeBeanReference("s3ResourceLoader"));
33+
34+
properties.add("s3Locations", locations);
35+
36+
registry.registerBeanDefinition("s3PropertyPlaceholderConfigurer", bd);
37+
}
38+
39+
}

src/main/java/com/spring/loader/S3PropertyPlaceholderConfigurer.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,41 @@
1212

1313
import com.amazonaws.services.s3.AmazonS3;
1414

15+
/**
16+
* Get S3 properties and add in spring context through {@link PropertyPlaceholderConfigurer} spring bean.
17+
*
18+
* It's possible to auto configure this bean with the {@link S3PropertiesLocation} annotation.
19+
*
20+
* @author Eric Dallo
21+
* @since 1.0.0
22+
* @see S3PropertiesLocation
23+
*/
1524
public class S3PropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
1625

17-
private S3ResourceLoader resourceLoader;
26+
private S3ResourceLoader s3ResourceLoader;
1827
private List<String> s3Locations = new ArrayList<>();
1928
private List<Resource> conventionalResources = new ArrayList<>();
2029

21-
public S3PropertyPlaceholderConfigurer(S3ResourceLoader resourceLoader) {
22-
this.resourceLoader = resourceLoader;
30+
@Deprecated // Spring eyes only
31+
S3PropertyPlaceholderConfigurer() {
32+
}
33+
34+
public S3PropertyPlaceholderConfigurer(S3ResourceLoader s3ResourceLoader) {
35+
this.s3ResourceLoader = s3ResourceLoader;
2336
}
24-
2537

2638
public S3PropertyPlaceholderConfigurer(AmazonS3 s3) {
2739
this(new S3ResourceLoader(s3));
2840
}
2941

30-
public void setS3Locations(String...s3Locations) {
42+
public void setS3ResourceLoader(S3ResourceLoader s3ResourceLoader) {
43+
this.s3ResourceLoader = s3ResourceLoader;
44+
}
45+
46+
public void setS3Locations(String... s3Locations) {
3147
this.s3Locations.addAll(asList(s3Locations));
3248
}
33-
49+
3450
@Override
3551
public void setLocations(Resource... locations) {
3652
this.conventionalResources.addAll(asList(locations));
@@ -43,14 +59,14 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
4359
if (total > 0) {
4460
List<Resource> allResources = new ArrayList<>();
4561
for (String location : s3Locations) {
46-
allResources.add(resourceLoader.getResource(location));
62+
allResources.add(s3ResourceLoader.getResource(location));
4763
}
48-
64+
4965
allResources.addAll(conventionalResources);
50-
66+
5167
super.setLocations(allResources.toArray(new Resource[allResources.size()]));
5268
}
53-
69+
5470
super.postProcessBeanFactory(beanFactory);
5571
}
5672
}

src/main/java/com/spring/loader/S3ResourceLoader.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
11
package com.spring.loader;
22

3-
import static java.lang.String.format;
4-
53
import org.springframework.core.io.InputStreamResource;
64
import org.springframework.core.io.Resource;
75
import org.springframework.core.io.ResourceLoader;
86
import org.springframework.util.StringUtils;
97

10-
import com.amazonaws.auth.AWSCredentials;
118
import com.amazonaws.services.s3.AmazonS3;
12-
import com.amazonaws.services.s3.AmazonS3Client;
139
import com.amazonaws.services.s3.model.S3Object;
10+
import com.spring.loader.exception.InvalidS3LocationException;
1411

12+
/**
13+
* Get {@link Resource} for the given aws s3 location.
14+
* For use with the {@link S3PropertiesLocation} annotation.
15+
*
16+
* @author Eric Dallo
17+
* @since 1.0.0
18+
* @see S3PropertyPlaceholderConfigurer
19+
*/
1520
class S3ResourceLoader implements ResourceLoader {
1621

1722
private static final String S3_PROTOCOL_PREFIX = "s3://";
18-
private final AmazonS3 s3;
23+
private final AmazonS3 amazonS3;
1924

20-
public S3ResourceLoader(AWSCredentials credentials) {
21-
this(new AmazonS3Client(credentials));
22-
}
23-
24-
public S3ResourceLoader(AmazonS3 s3) {
25-
this.s3 = s3;
25+
S3ResourceLoader(AmazonS3 amazonS3) {
26+
this.amazonS3 = amazonS3;
2627
}
2728

2829
@Override
2930
public Resource getResource(String location) {
3031
if (StringUtils.isEmpty(location)) {
31-
throw new S3ResourceException("Location cannot be empty or null");
32+
throw new InvalidS3LocationException("Location cannot be empty or null");
3233
}
3334

34-
if (!location.startsWith(S3_PROTOCOL_PREFIX)) {
35-
throw new S3ResourceException(format("%s does not starts with '%s'", location, S3_PROTOCOL_PREFIX));
35+
String path = location.startsWith(S3_PROTOCOL_PREFIX) ? location.substring(S3_PROTOCOL_PREFIX.length(), location.length()) : location;
36+
37+
if(!path.contains("/")) {
38+
throw new InvalidS3LocationException("The location must contains the full path of the properties file");
3639
}
3740

38-
String path = location.substring(S3_PROTOCOL_PREFIX.length(), location.length());
3941
String bucketName = path.substring(0, path.indexOf('/'));
4042
String keyName = path.substring(path.indexOf('/') + 1);
4143

4244
try {
43-
S3Object s3Object = s3.getObject(bucketName, keyName);
45+
S3Object s3Object = amazonS3.getObject(bucketName, keyName);
4446
return new InputStreamResource(s3Object.getObjectContent(), location);
4547
} catch (Exception e) {
4648
throw new S3ResourceException("Could not load resource from " + location, e);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.spring.loader;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
6+
import com.amazonaws.services.s3.AmazonS3;
7+
8+
@Configuration
9+
class S3ResourseLoaderConfiguration {
10+
11+
@Bean
12+
S3ResourceLoader s3ResourceLoader(AmazonS3 amazonS3) {
13+
return new S3ResourceLoader(amazonS3);
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.spring.loader.exception;
2+
3+
import com.spring.loader.S3ResourceException;
4+
5+
public class InvalidS3LocationException extends S3ResourceException {
6+
private static final long serialVersionUID = 2057112790586228669L;
7+
8+
public InvalidS3LocationException(String message) {
9+
super(message);
10+
}
11+
12+
}
Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.spring.loader;
22

3+
import static org.mockito.Matchers.anyString;
34
import static org.mockito.Mockito.never;
45
import static org.mockito.Mockito.verify;
56
import static org.mockito.Mockito.when;
@@ -8,12 +9,12 @@
89
import org.junit.Test;
910
import org.junit.runner.RunWith;
1011
import org.mockito.Mock;
11-
import org.mockito.Mockito;
1212
import org.mockito.runners.MockitoJUnitRunner;
1313

1414
import com.amazonaws.services.s3.AmazonS3;
1515
import com.amazonaws.services.s3.model.S3Object;
1616
import com.amazonaws.services.s3.model.S3ObjectInputStream;
17+
import com.spring.loader.exception.InvalidS3LocationException;
1718

1819
@RunWith(MockitoJUnitRunner.class)
1920
public class S3ResourceLoaderTest {
@@ -27,8 +28,9 @@ public class S3ResourceLoaderTest {
2728
@Mock
2829
private S3ObjectInputStream inputStream;
2930

30-
private String validLocation = "s3://mybucket/myfolder";
31-
private String invalidLocation = "mybucket/myfolder";
31+
private String validLocationWithPrefix = "s3://mybucket/myfolder";
32+
private String validLocationWithoutPrefix = "mybucket/myfolder";
33+
private String invalidLocation = "mybucket";
3234
private String emptyLocation = "";
3335

3436
@Before
@@ -37,33 +39,43 @@ public void setup() {
3739
}
3840

3941
@Test
40-
public void shouldGetAValidResource() {
41-
when(s3.getObject(Mockito.anyString(), Mockito.anyString())).thenReturn(s3Object);
42+
public void shouldGetAValidResourceWithPrefix() {
43+
when(s3.getObject(anyString(), anyString())).thenReturn(s3Object);
4244
when(s3Object.getObjectContent()).thenReturn(inputStream);
4345

44-
subject.getResource(validLocation);
46+
subject.getResource(validLocationWithPrefix);
4547

46-
verify(s3).getObject(Mockito.anyString(), Mockito.anyString());
48+
verify(s3).getObject(anyString(), anyString());
4749
}
4850

49-
@Test(expected = S3ResourceException.class)
51+
@Test
52+
public void shouldGetAValidResourceWithoutPrefix() {
53+
when(s3.getObject(anyString(), anyString())).thenReturn(s3Object);
54+
when(s3Object.getObjectContent()).thenReturn(inputStream);
55+
56+
subject.getResource(validLocationWithoutPrefix);
57+
58+
verify(s3).getObject(anyString(), anyString());
59+
}
60+
61+
@Test(expected = InvalidS3LocationException.class)
5062
public void shouldNotGetAValidResourceWhenLocationIsEmpty() {
51-
when(s3.getObject(Mockito.anyString(), Mockito.anyString())).thenReturn(s3Object);
63+
when(s3.getObject(anyString(), anyString())).thenReturn(s3Object);
5264
when(s3Object.getObjectContent()).thenReturn(inputStream);
5365

5466
subject.getResource(emptyLocation);
5567

56-
verify(s3, never()).getObject(Mockito.anyString(), Mockito.anyString());
68+
verify(s3, never()).getObject(anyString(), anyString());
5769
}
5870

59-
@Test(expected = S3ResourceException.class)
71+
@Test(expected = InvalidS3LocationException.class)
6072
public void shouldNotGetAValidResourceWhenLocationIsInvald() {
61-
when(s3.getObject(Mockito.anyString(), Mockito.anyString())).thenReturn(s3Object);
73+
when(s3.getObject(anyString(), anyString())).thenReturn(s3Object);
6274
when(s3Object.getObjectContent()).thenReturn(inputStream);
6375

6476
subject.getResource(invalidLocation);
6577

66-
verify(s3, never()).getObject(Mockito.anyString(), Mockito.anyString());
78+
verify(s3, never()).getObject(anyString(), anyString());
6779
}
6880

6981
}

0 commit comments

Comments
 (0)