Repository: awspring/spring-cloud-aws (Java)

Issue:
Create a S3Resource for upload in a more concise way
**Type**: Feature

**Is your feature request related to a problem? Please describe.**
I'm trying to create an excel file and upload it to S3. I'm using [fastexcel](https://github.com/dhatim/fastexcel) and it requires an `OutputStream` to create a `Workbook` which is the representation of an excel file. What I'm doing looks like..
```
S3Resource s3Resource = new S3Resource(bucket, key, s3Client, s3OutputStreamProvider);
try (Workbook workbook = new Workbook(s3Resource.getOutputStream(), "name", "version")) {
  ..
}
```
For this to happen I have to autowire `S3Client` and `S3OutputStreamProvider` and pass them to the constructor as above, which I find a little verbose when I'm happy with using the default ones.
I checked `S3Template.upload()` but it requires an `InputStream` and I couldn't find a way to utilize it in this case.

**Describe the solution you'd like**
Since `S3Template` already has `S3Client` and `S3OutputStreamProvider`, something like `S3Resource s3Resource = s3Template.createResource(bucket, key)` would be very convenient and easy to read.


Discussion:

Patch:
diff --git a/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Operations.java b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Operations.java
index 6c2c10945..7bdf6c673 100644
--- a/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Operations.java
+++ b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Operations.java
@@ -100,6 +100,18 @@ public interface S3Operations {
 	 */
 	S3Resource store(String bucketName, String key, Object object);
 
+	/**
+	 * Creates an {@link S3Resource} for given bucket name and object key using {@link S3OutputStreamProvider}
+	 * configured on the implementation class ({@link S3Template}).
+	 * <p>
+	 * Note that calling this method does not create an actual object on S3.
+	 *
+	 * @param bucketName - the bucket name
+	 * @param key - the object key
+	 * @return the {@link S3Resource}
+	 */
+	S3Resource createResource(String bucketName, String key);
+
 	/**
 	 * Reads a Java object from a S3 bucket. Uses {@link S3ObjectConverter} for deserialization.
 	 *
diff --git a/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Template.java b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Template.java
index 9ebf451a9..fa8e3ae62 100644
--- a/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Template.java
+++ b/spring-cloud-aws-s3/src/main/java/io/awspring/cloud/s3/S3Template.java
@@ -140,6 +140,11 @@ public S3Resource store(String bucketName, String key, Object object) {
 		return new S3Resource(bucketName, key, s3Client, s3OutputStreamProvider);
 	}
 
+	@Override
+	public S3Resource createResource(String bucketName, String key) {
+		return new S3Resource(bucketName, key, s3Client, s3OutputStreamProvider);
+	}
+
 	@Override
 	public <T> T read(String bucketName, String key, Class<T> clazz) {
 		Assert.notNull(bucketName, "bucketName is required");


Test patch:
diff --git a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3TemplateTests.java b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3TemplateTests.java
index 53b072834..97d0ba501 100644
--- a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3TemplateTests.java
+++ b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3TemplateTests.java
@@ -18,6 +18,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -77,4 +78,17 @@ void throwsExceptionWhenReadFails() {
 		});
 	}
 
+	@Test
+	void createsS3Resource() throws IOException {
+		S3OutputStream outputStream = mock(S3OutputStream.class);
+		when(s3OutputStreamProvider.create(eq("bucket"), eq("key"), any())).thenReturn(outputStream);
+
+		S3Resource resource = s3Template.createResource("bucket", "key");
+
+		assertThat(resource).isNotNull();
+		assertThat(resource.getOutputStream()).isEqualTo(outputStream);
+		assertThat(resource.getLocation().getBucket()).isEqualTo("bucket");
+		assertThat(resource.getLocation().getObject()).isEqualTo("key");
+	}
+
 }