9. Resource handling

The Spring Framework provides a org.springframework.core.io.ResourceLoader abstraction to load files from the filesystem, servlet context and the classpath. Spring Cloud AWS adds support for the Amazon S3 service to load and write resources with the resource loader and the s3 protocol.

The resource loader is part of the context module, therefore no additional dependencies are necessary to use the resource handling support.

9.1 Configuring the resource loader

Spring Cloud AWS does not modify the default resource loader unless it encounters an explicit configuration with an XML namespace element. The configuration consists of one element for the whole application context that is shown below:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:aws-context="http://www.springframework.org/schema/cloud/aws/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/cloud/aws/context
	   http://www.springframework.org/schema/cloud/aws/context/spring-cloud-aws-context.xsd">

	<aws-context:context-credentials>
    		...
        </aws-context:context-credentials>

	<aws-context:context-resource-loader/>
</beans>

9.2 Downloading files

Downloading files can be done by using the s3 protocol to reference Amazon S3 buckets and objects inside their bucket. The typical pattern is s3://<bucket>/<object> where bucket is the global and unique bucket name and object is a valid object name inside the bucket. The object name can be a file in the root folder of a bucket or a nested file within a directory inside a bucket.

The next example demonstrates the use of the resource loader to load different resources.

public class SimpleResourceLoadingBean {

	@Autowired
	private ResourceLoader resourceLoader;

	public void resourceLoadingMethod() throws IOException {
		Resource resource = this.resourceLoader.getResource("s3://myBucket/rootFile.log");
		Resource secondResource = this.resourceLoader.getResource("s3://myBucket/rootFolder/subFile");

		InputStream inputStream = resource.getInputStream();
		//read file
	}
}

9.3 Uploading files

Since Spring Framework 3.1 the resource loader can also be used to upload files with the org.springframework.core.io.WritableResource interface which is a specialization of the org.springframework.core.io.ResourceLoader interface. Clients can upload files using the WritableResource interface. The next example demonstrates an upload of a resource using the resource loader.

public class SimpleResourceLoadingBean {

    @Autowired
    private ResourceLoader resourceLoader;

    public void writeResource() throws IOException {
        Resource resource = this.resourceLoader.getResource("s3://myBucket/rootFile.log");
        WritableResource writableResource = (WritableResource) resource;
        try (OutputStream outputStream = writableResource.getOutputStream()) {
            outputStream.write("test".getBytes());
        }
    }
}

9.3.1 Uploading multi-part files

Amazon S3 supports multi-part uploads to increase the general throughput while uploading. Spring Cloud AWS by default only uses one thread to upload the files and therefore does not provide parallel upload support. Users can configure a custom org.springframework.core.task.TaskExecutor for the resource loader. The resource loader will queue multiple threads at the same time to use parallel multi-part uploads.

The configuration for a resource loader that uploads with 10 Threads looks like the following

<beans ...>
  <aws-context:context-resource-loader task-executor="executor" />
  <task:executor id="executor" pool-size="10" queue-capacity="0" rejection-policy="CALLER_RUNS" />
</beans>
[Warning]Warning

Spring Cloud AWS consumes up to 5 MB (at a minimum) of memory per thread. Therefore each parallel thread will incur a memory footprint of 5 MB in the heap, and a thread size of 10 will consume therefore up to 50 mb of heap space. Spring Cloud AWS releases the memory as soon as possible. Also, the example above shows that there is no queue-capacity configured, because queued requests would also consume memory.

9.3.2 Uploading with the TransferManager

The Amazon SDK also provides a high-level abstraction that is useful to upload files, also with multiple threads using the multi-part functionality. A com.amazonaws.services.s3.transfer.TransferManager can be easily created in the application code and injected with the pre-configured com.amazonaws.services.s3.AmazonS3 client that is already created with the Spring Cloud AWS resource loader configuration.

This example shows the use of the transferManager within an application to upload files from the hard-drive.

public class SimpleResourceLoadingBean {

	@Autowired
	private AmazonS3 amazonS3;

	public void withTransferManager() {
		TransferManager transferManager = new TransferManager(this.amazonS3);
		transferManager.upload("myBucket","filename",new File("someFile"));
	}
}

9.4 Searching resources

The Spring resource loader also supports collecting resources based on an Ant-style path specification. Spring Cloud AWS offers the same support to resolve resources within a bucket and even throughout buckets. The actual resource loader needs to be wrapped with the Spring Cloud AWS one in order to search for s3 buckets, in case of non s3 bucket the resource loader will fall back to the original one. The next example shows the resource resolution by using different patterns.

public class SimpleResourceLoadingBean {

	private ResourcePatternResolver resourcePatternResolver;

 	@Autowired
    public void setupResolver(ApplicationContext applicationContext, AmazonS3 amazonS3){
        this.resourcePatternResolver = new PathMatchingSimpleStorageResourcePatternResolver(amazonS3, applicationContext);
    }

 	public void resolveAndLoad() throws IOException {
 		Resource[] allTxtFilesInFolder =  this.resourcePatternResolver.getResources("s3://bucket/name/*.txt");
 		Resource[] allTxtFilesInBucket =  this.resourcePatternResolver.getResources("s3://bucket/**/*.txt");
 		Resource[] allTxtFilesGlobally =  this.resourcePatternResolver.getResources("s3://**/*.txt");
 	}
}
[Warning]Warning

Resolving resources throughout all buckets can be very time consuming depending on the number of buckets a user owns.

9.5 Using CloudFormation

CloudFormation also allows to create buckets during stack creation. These buckets will typically have a generated name that must be used as the bucket name. In order to allow application developers to define static names inside their configuration, Spring Cloud AWS provides support to resolve the generated bucket names. Application developers can use the org.springframework.cloud.aws.core.env.ResourceIdResolver interface to resolve the physical names that are generated based on the logical names.

The next example shows a bucket definition inside a CloudFormation stack template. The bucket will be created with a name like integrationteststack-sampleBucket-23qysofs62tc2

{
	"Resources": {
		"sampleBucket": {
			"Type": "AWS::S3::Bucket"
		}
	}
}

Application developers can resolve that name and use it to load resources as shown in the next example below.

public class SimpleResourceLoadingBean {

	private final ResourceLoader loader;
	private final ResourceIdResolver idResolver;

	@Autowired
	public SimpleResourceLoadingBean(ResourceLoader loader, ResourceIdResolver idResolver) {
		this.loader = loader;
		this.idResolver = idResolver;
	}

	public void resolveAndLoad() {
		String sampleBucketName = this.idResolver.
			resolveToPhysicalResourceId("sampleBucket");
		Resource resource = this.loader.
			getResource("s3://" + sampleBucketName + "/test");
	}
}