diff --git a/build.gradle b/build.gradle
index e2f5769e8..e6b8210a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,6 +50,14 @@ dependencies {
testImplementation 'org.testcontainers:mysql'
checkstyle 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.41'
checkstyle 'com.puppycrawl.tools:checkstyle:10.12.5'
+
+ implementation platform('io.awspring.cloud:spring-cloud-aws-dependencies:3.1.0')
+ implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'
+
+ implementation platform("software.amazon.awssdk:bom:2.24.13")
+ implementation 'software.amazon.awssdk:kms'
+ implementation 'software.amazon.awssdk:sso'
+ implementation 'software.amazon.awssdk:ssooidc'
}
tasks.named('test') {
diff --git a/pom.xml b/pom.xml
index cf47f8aa6..1bdb6b53a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,8 +35,28 @@
0.0.11
0.0.40
+ 2.24.13
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 3.1.0
+ pom
+ import
+
+
+
+ software.amazon.awssdk
+ bom
+ ${aws.java.sdk.version}
+ pom
+ import
+
+
+
@@ -139,6 +159,25 @@
jakarta.xml.bind-api
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-s3
+
+
+
+ software.amazon.awssdk
+ kms
+
+
+
+ software.amazon.awssdk
+ sso
+
+
+
+ software.amazon.awssdk
+ ssooidc
+
@@ -220,7 +259,7 @@
spring-boot-maven-plugin
-
build-info
@@ -378,7 +417,7 @@
-
org.eclipse.m2e
@@ -436,4 +475,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java b/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java
new file mode 100644
index 000000000..265e60095
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java
@@ -0,0 +1,87 @@
+package org.springframework.samples.petclinic.pettypes;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.samples.petclinic.owner.PetType;
+import org.springframework.stereotype.Component;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.s3.S3Client;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Perform some initializing of the supported pet types on startup by downloading them
+ * from S3, if enabled
+ */
+@Component
+public class InitPetTypes implements InitializingBean {
+
+ private final Logger logger = LoggerFactory.getLogger(InitPetTypes.class);
+
+ private final PetTypesRepository petTypesRepository;
+
+ private final KmsClient kmsClient;
+
+ private final S3Client s3Client;
+
+ @Value("${app.init.pet-types.key:petclinic-pettypes.txt}")
+ private String petTypesInitObjectKey;
+
+ @Value("${app.init.pet-types.kms-encrypted:false}")
+ private Boolean petTypesInitObjectKmsEncrypted;
+
+ InitPetTypes(PetTypesRepository petTypesRepository, Optional awsCredentialsProvider,
+ Optional s3Client) {
+ this.petTypesRepository = petTypesRepository;
+ this.s3Client = s3Client.orElse(null);
+
+ AwsCredentialsProvider credentialsProvider = awsCredentialsProvider.orElse(null);
+ this.kmsClient = credentialsProvider != null
+ ? KmsClient.builder().credentialsProvider(credentialsProvider).build() : null;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (s3Client == null) {
+ logger.info("No S3Client configured, skipping loading pettypes.");
+ return;
+ }
+
+ logger.info("Loading Pet types from \"spring-petclinic-init\" bucket at " + petTypesInitObjectKey);
+ String fileContents = s3Client
+ .getObjectAsBytes(b -> b.bucket("spring-petclinic-init").key(petTypesInitObjectKey))
+ .asUtf8String();
+
+ // if kms encrypted, attempt to decrypt it
+ if (petTypesInitObjectKmsEncrypted) {
+ logger.info("Decrypting pet types using KMS key...");
+ SdkBytes encryptedData = SdkBytes.fromUtf8String(fileContents);
+ DecryptResponse decryptResponse = kmsClient
+ .decrypt(b -> b.ciphertextBlob(encryptedData).keyId("spring-petclinic-init"));
+ fileContents = decryptResponse.plaintext().asUtf8String();
+ }
+
+ List foundTypes = fileContents.lines().map(s -> {
+ PetType type = new PetType();
+ type.setName(s);
+ return type;
+ }).toList();
+ logger.info("Found " + foundTypes.size() + " pet types");
+
+ // load the found types into the database
+ if (!foundTypes.isEmpty()) {
+ petTypesRepository.saveAllAndFlush(foundTypes);
+
+ // clean up the file if we've successfully loaded from it
+ logger.info("Deleting Pet types file from S3");
+ s3Client.deleteObject(builder -> builder.bucket("spring-petclinic-init").key(petTypesInitObjectKey));
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java b/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java
new file mode 100644
index 000000000..d0c17a843
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java
@@ -0,0 +1,8 @@
+package org.springframework.samples.petclinic.pettypes;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.samples.petclinic.owner.PetType;
+
+public interface PetTypesRepository extends JpaRepository {
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 5d3eeed32..99f4a977d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -23,3 +23,6 @@ logging.level.org.springframework=INFO
# Maximum time static resources should be cached
spring.web.resources.cache.cachecontrol.max-age=12h
+
+spring.cloud.aws.credentials.profile.name=${SPRING_AWS_PROFILE:dev}
+spring.cloud.aws.s3.enabled=false