Skip to content

Commit

Permalink
Spring AWS S3 (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrodovale authored Nov 18, 2024
1 parent 0114807 commit 86e6d9d
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 17 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ module for each Spring feature that I test.
## Table of contents
- [Events in Spring](spring-events/README.md);
- [SpEL permission evaluator](spring-el-permission-evaluator/README.md)
- [AWS S3](spring-aws-s3/README.md)
34 changes: 17 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.pedrodovale.experimentingwith.spring</groupId>
<artifactId>experimenting-with-spring-parent</artifactId>
<version>0.1-SNAPSHOT</version>
<modules>
<module>spring-events</module>
<module>spring-el-permission-evaluator</module>
</modules>
<packaging>pom</packaging>
<groupId>com.pedrodovale.experimentingwith.spring</groupId>
<artifactId>experimenting-with-spring-parent</artifactId>
<version>0.1-SNAPSHOT</version>
<modules>
<module>spring-events</module>
<module>spring-aws-s3</module>
</modules>
<packaging>pom</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

</project>
21 changes: 21 additions & 0 deletions spring-aws-s3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AWS S3

Playing around with AWS S3 in a Springboot application based
on [this documentation](https://docs.awspring.io/spring-cloud-aws/docs/3.2.0/reference/html/index.html#starter-dependencies)

## How to test it

From the root folder, execute:

> ./mvnw clean package \
> java -jar spring-aws-s3/target/app.jar --spring.config.additional-location=spring-aws-s3/src/main/resources/aws_credentials.properties
This will start the Springboot application on port 8080.
To interact with the API:

> curl -v --include --form books=@spring-aws-s3/src/test/resources/books.json http://localhost:8080/books/import
> --data '{"title":"A Study in Scarlet"}'
> curl http://localhost:8080/books
The response body should contain the imported books, populated with an `id`.
81 changes: 81 additions & 0 deletions spring-aws-s3/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pedrodovale.experimentingwith.spring</groupId>
<artifactId>experimenting-with-spring-parent</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>

<artifactId>spring-aws-s3</artifactId>
<properties>
<spring-boot-dependencies.version>3.3.5</spring-boot-dependencies.version>
<spring-cloud-aws-dependencies.version>3.2.0</spring-cloud-aws-dependencies.version>
<spring-boot-maven-plugin.version></spring-boot-maven-plugin.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${spring-cloud-aws-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-s3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>

<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>package-with-build-info</id>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pedrodovale.experimentingwith.spring.awss3;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(BooksProperties.class)
public class AwsS3Application {
public static void main(String[] args) {
SpringApplication.run(AwsS3Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.pedrodovale.experimentingwith.spring.awss3;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import software.amazon.awssdk.annotations.NotNull;

import static com.pedrodovale.experimentingwith.spring.awss3.BooksProperties.PREFIX;

@ConfigurationProperties(prefix = PREFIX)
@Getter
@Setter
public class BooksProperties {

public static final String PREFIX = "com.pedrodovale.experimentingwith.spring.books";

@NotNull private String bucket;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.pedrodovale.experimentingwith.spring.awss3.controller;

import com.pedrodovale.experimentingwith.spring.awss3.model.Book;
import com.pedrodovale.experimentingwith.spring.awss3.service.BooksService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@RestController
@RequiredArgsConstructor
public class BooksController {

private final BooksService booksService;

@PostMapping(
value = "/books/import",
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
public List<Book> importBooks(@RequestPart(name = "books") MultipartFile multipartFile) {
return booksService.processAndBackup(multipartFile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pedrodovale.experimentingwith.spring.awss3.model;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Getter
@Setter
public class Book implements Serializable {
private String id;
private String title;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.pedrodovale.experimentingwith.spring.awss3.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pedrodovale.experimentingwith.spring.awss3.BooksProperties;
import com.pedrodovale.experimentingwith.spring.awss3.model.Book;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

@Service
public class BooksService {

public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final S3Client s3Client;
private final String bucket;

public BooksService(S3Client s3Client, BooksProperties booksProperties) {
this.s3Client = s3Client;
this.bucket = booksProperties.getBucket();
}

public List<Book> processAndBackup(MultipartFile multipartFile) {
try (InputStream inputStream = multipartFile.getInputStream()) {
byte[] bytes = inputStream.readAllBytes();
List<Book> books = OBJECT_MAPPER.readValue(bytes, new TypeReference<>() {});
books.forEach(
book -> {
String bookId = UUID.randomUUID().toString();
book.setId(bookId);
s3Client.putObject(
PutObjectRequest.builder().key(bookId).bucket(bucket).build(),
RequestBody.fromBytes(bytes));
});
return books;
} catch (IOException e) {
throw new RuntimeException("problem retrieving local file", e);
}
}
}
20 changes: 20 additions & 0 deletions spring-aws-s3/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
spring:
cloud:
aws:
region:
static: eu-central-1
s3:
path-style-access-enabled: false

com:
pedrodovale:
experimentingwith:
spring:
books:
bucket: books-123

logging:
level:
io:
awspring:
cloud: debug
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.pedrodovale.experimentingwith.spring.awss3.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.io.Resource;
import org.springframework.mock.web.MockPart;
import org.springframework.test.web.servlet.MockMvc;
import software.amazon.awssdk.services.s3.S3Client;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class BooksControllerTest {

@Autowired private MockMvc mockMvc;

@MockBean private S3Client s3Client;

@Value("classpath:books.json")
private Resource sampleFile;

@Test
void importBooks() throws Exception {
mockMvc
.perform(
multipart("/books/import")
.part(
new MockPart(
"books", sampleFile.getFilename(), sampleFile.getContentAsByteArray())))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath("$[0].id").isNotEmpty())
.andExpect(jsonPath("$[0].title").value("A Study In Scarlet"));
}
}
5 changes: 5 additions & 0 deletions spring-aws-s3/src/test/resources/books.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"title": "A Study In Scarlet"
}
]

0 comments on commit 86e6d9d

Please sign in to comment.