From 7860cb7fe1077cde830fdb05614b9d2317767b23 Mon Sep 17 00:00:00 2001
From: Hai Phuc Nguyen <“3423575+haiphucnguyen@users.noreply.github.com”>
Date: Fri, 13 Jun 2025 00:34:27 -0700
Subject: [PATCH 1/2] Update
Update
Update
Update
Update
Update
Update
Update
Update
Update
Update
Update
Update
Update
---
.github/workflows/gradle.yml | 4 +-
README.md | 74 +++----
examples/spring-postgresql/build.gradle.kts | 1 -
.../examples/postgresql/PostgresqlTest.java | 2 +-
examples/springboot-mysql/build.gradle.kts | 4 +-
examples/springboot-ollama/build.gradle.kts | 37 ++++
.../examples/ollama/OllamaDemoApp.java | 13 ++
.../ollama/controller/ChatController.java | 42 ++++
.../src/main/resources/application.yml | 21 ++
.../examples/ollama/OllamaDemoAppTest.java | 68 +++++++
.../src/test/resources/application-test.yml | 10 +
.../src/test/resources/logback.xml | 28 +++
.../springboot-postgresql/build.gradle.kts | 2 -
gradle.properties | 2 +-
gradle/libs.versions.toml | 13 +-
.../jdbc/mysql/MySqlContainerProvider.java | 53 +----
...stcontainers.SpringAwareContainerProvider} | 0
modules/ollama/build.gradle.kts | 21 ++
.../ai/OllamaContainerProvider.java | 93 +++++++++
...estcontainers.SpringAwareContainerProvider | 1 +
.../PostgreSqlContainerProvider.java | 54 +-----
...stcontainers.SpringAwareContainerProvider} | 0
settings.gradle.kts | 2 +
.../ContainerContextCustomizerFactory.java | 42 ++++
.../ContainerLifecycleExtension.java | 81 ++++++++
.../testcontainers/ContainerRegistry.java | 72 +++++++
.../testcontainers/ContainerType.java | 22 +++
.../testcontainers/LifecycleAware.java | 31 +++
.../ServiceLoaderContainerFactory.java | 50 +++++
.../SpringAwareContainerProvider.java | 97 ++++++++++
.../ai/EnableOllamaContainer.java | 85 ++++++++
.../ai/OllamaContainerExtension.java | 53 +++++
.../testcontainers/ai/OllamaOptions.java | 50 +++++
.../jdbc/EnableJdbcContainer.java | 35 ++--
.../testcontainers/jdbc/EnableMySQL.java | 47 +----
.../testcontainers/jdbc/EnablePostgreSQL.java | 18 +-
...JdbcContainerContextCustomizerFactory.java | 93 ---------
.../jdbc/JdbcContainerExtension.java | 107 ++++++++++
.../jdbc/JdbcContainerProvider.java | 63 ------
.../jdbc/JdbcContainerProviderFactory.java | 61 ------
.../jdbc/JdbcContainerRegistry.java | 137 -------------
.../testcontainers/jdbc/JdbcExtension.java | 183 ------------------
.../testcontainers/jdbc/Rdbms.java | 27 ---
.../SpringAwareJdbcContainerProvider.java | 134 ++-----------
.../main/resources/META-INF/spring.factories | 2 +-
45 files changed, 1159 insertions(+), 876 deletions(-)
create mode 100644 examples/springboot-ollama/build.gradle.kts
create mode 100644 examples/springboot-ollama/src/main/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoApp.java
create mode 100644 examples/springboot-ollama/src/main/java/io/flowinquiry/testcontainers/examples/ollama/controller/ChatController.java
create mode 100644 examples/springboot-ollama/src/main/resources/application.yml
create mode 100644 examples/springboot-ollama/src/test/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoAppTest.java
create mode 100644 examples/springboot-ollama/src/test/resources/application-test.yml
create mode 100644 examples/springboot-ollama/src/test/resources/logback.xml
rename modules/mysql/src/main/resources/META-INF/services/{io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider => io.flowinquiry.testcontainers.SpringAwareContainerProvider} (100%)
create mode 100644 modules/ollama/build.gradle.kts
create mode 100644 modules/ollama/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerProvider.java
create mode 100644 modules/ollama/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider
rename modules/postgresql/src/main/resources/META-INF/services/{io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider => io.flowinquiry.testcontainers.SpringAwareContainerProvider} (100%)
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerContextCustomizerFactory.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerLifecycleExtension.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerRegistry.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerType.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/LifecycleAware.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ServiceLoaderContainerFactory.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/SpringAwareContainerProvider.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/EnableOllamaContainer.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerExtension.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/OllamaOptions.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerContextCustomizerFactory.java
create mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerExtension.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerProvider.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerProviderFactory.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerRegistry.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcExtension.java
delete mode 100644 spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/Rdbms.java
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index ac1865a..6e3f45d 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -23,4 +23,6 @@ jobs:
- name: Test with Gradle
uses: gradle/gradle-build-action@v2
with:
- arguments: test
\ No newline at end of file
+ arguments: test
+ env:
+ TESTCONTAINERS_REUSE_ENABLE: true
\ No newline at end of file
diff --git a/README.md b/README.md
index 75704b8..1fe375c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# Spring-TestContainers
-Spring-TestContainers is a Java library that simplifies database integration testing by automating Testcontainers setup and lifecycle management—seamlessly integrated with Spring and Spring Boot.
+Spring-TestContainers is a Java library that makes it easier to write integration tests with Testcontainers, especially when you're using Spring or Spring Boot. It handles the setup and lifecycle of containers for you, so you can focus on testing—not boilerplate.
+
+We originally built this for FlowInquiry to make our own testing smoother. It worked so well, we decided to share it as a standalone library so other teams can take advantage of it too.
## Why Spring-TestContainers?
@@ -12,6 +14,19 @@ Setting up Testcontainers in Spring-based projects often involves boilerplate co
* Auto-configures Spring environment with database connection details
+### Supported Containers
+
+Spring-TestContainers provides out-of-the-box support for the following containers. You can enable each one via a dedicated annotation in your test classes:
+
+Spring-TestContainers provides out-of-the-box support for the following containers. You can enable each one via a dedicated annotation in your test classes:
+
+| Container | Annotation | Example Usage | Notes |
+|----------------|-----------------------------------|-----------------------------------------------------|--------------------------------|
+| **PostgreSQL** | `@EnablePostgresContainer` | `@EnablePostgresContainer(version = "15")` | Uses `PostgreSQLContainer` |
+| **MySQL** | `@EnableMySQLContainer` | `@EnableMySQLContainer(version = "8")` | Uses `MySQLContainer` |
+| **Ollama (AI)**| `@EnableOllamaContainer` | `@EnableOllamaContainer(model = "llama2")` | Starts Ollama with auto-pull |
+
+
## Comparison: TestContainers with Spring vs Spring-TestContainers
The following table demonstrates the difference between using TestContainers with Spring directly and using Spring-TestContainers:
@@ -103,7 +118,7 @@ class PostgresTest {
## Features
-* 🧩 Simple annotation API: @EnablePostgreSQL, @EnableMySQL
+* 🧩 Simple annotation API: @EnablePostgreSQL, @EnableMySQL, @EnableOllmaContainer
* 🔄 Automatic container lifecycle management
@@ -130,30 +145,19 @@ Add the core library along with the database module(s) you plan to use. Each dat
### Gradle (Kotlin DSL)
```kotlin
-// Core library
-testImplementation("io.flowinquiry.testcontainers:spring-testcontainers:0.9.0")
-
// Add one or more of the following database modules
-testImplementation("io.flowinquiry.testcontainers:postgresql:0.9.0") // PostgreSQL support
-testImplementation("io.flowinquiry.testcontainers:mysql:0.9.0") // MySQL support
-
-// Corresponding TestContainers dependencies
-testImplementation("org.testcontainers:postgresql:1.21.0")
-testImplementation("org.testcontainers:mysql:1.21.0")
+testImplementation("io.flowinquiry.testcontainers:postgresql:0.9.1") // PostgreSQL support
+testImplementation("io.flowinquiry.testcontainers:mysql:0.9.1") // MySQL support
+testImplementation("io.flowinquiry.testcontainers:ollama:0.9.1") // Ollama support
```
### Maven
```xml
-
-
This class provides support for MySQL database containers in the Spring TestContainers - * framework. It creates and manages a {@link MySQLContainer} instance, which is a TestContainers - * implementation for MySQL databases. - * - *
This provider is automatically discovered by the {@link - * io.flowinquiry.testcontainers.jdbc.JdbcContainerProviderFactory} using Java's ServiceLoader - * mechanism when a test class is annotated with {@code @EnableMySQL}. - * - *
The provider handles: - * - *
This implementation returns {@link Rdbms#MYSQL}, indicating that this provider supports - * MySQL databases. - * - * @return {@link Rdbms#MYSQL} as the supported database type - */ @Override - public Rdbms getType() { - return Rdbms.MYSQL; + public ContainerType getContainerType() { + return MYSQL; } - /** - * Creates a new MySQL database container. - * - *
This method creates a {@link MySQLContainer} instance configured with the Docker image and - * version specified in the {@code @EnableMySQL} annotation on the test class. - * - *
The container is created but not started. The {@link #start()} method must be called to - * start the container before it can be used. - * - * @return a new {@link MySQLContainer} instance - */ @Override - protected MySQLContainer> createJdbcDatabaseContainer() { + protected JdbcDatabaseContainer> createContainer() { return new MySQLContainer<>(dockerImage + ":" + version); } } diff --git a/modules/mysql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider b/modules/mysql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider similarity index 100% rename from modules/mysql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider rename to modules/mysql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider diff --git a/modules/ollama/build.gradle.kts b/modules/ollama/build.gradle.kts new file mode 100644 index 0000000..4d7c05d --- /dev/null +++ b/modules/ollama/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("buildlogic.java-library-conventions") +} + +repositories { + mavenCentral() +} + +dependencies { + api(project(":spring-testcontainers")) + implementation(platform(libs.spring.bom)) + implementation(libs.testcontainers.ollama) + implementation(libs.spring.context) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testImplementation(libs.junit.platform.launcher) +} + +tasks.test { + useJUnitPlatform() +} diff --git a/modules/ollama/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerProvider.java b/modules/ollama/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerProvider.java new file mode 100644 index 0000000..ea3b7ad --- /dev/null +++ b/modules/ollama/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerProvider.java @@ -0,0 +1,93 @@ +package io.flowinquiry.testcontainers.ai; + +import static io.flowinquiry.testcontainers.ContainerType.OLLAMA; + +import io.flowinquiry.testcontainers.ContainerType; +import io.flowinquiry.testcontainers.SpringAwareContainerProvider; +import java.io.IOException; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.testcontainers.ollama.OllamaContainer; + +/** + * A Spring-aware container provider for Ollama AI containers. + * + *
This class manages the lifecycle of Ollama containers in a Spring environment, including + * container creation, configuration, and integration with Spring's property system. It handles + * pulling the specified AI model during container startup and configuring Spring AI properties to + * connect to the containerized Ollama instance. + * + *
The provider uses the {@link EnableOllamaContainer} annotation to configure the container with
+ * specific parameters such as the Docker image, version, model name, and model options (temperature
+ * and top-p values).
+ */
+public class OllamaContainerProvider
+ extends SpringAwareContainerProvider This method first calls the parent class's start method to start the container, then
+ * executes the 'ollama pull' command inside the container to download the specified AI model.
+ *
+ * @throws RuntimeException if there is an error pulling the model
+ */
+ @Override
+ public void start() {
+ super.start();
+ try {
+ log.info("Starting pull model {}", enableContainerAnnotation.model());
+ container.execInContainer("ollama", "pull", enableContainerAnnotation.model());
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Applies Ollama-specific configuration to the Spring environment.
+ *
+ * These properties are added to the Spring environment with high precedence to ensure they
+ * override any existing configuration.
+ *
+ * @param environment the Spring environment to configure
+ */
+ @Override
+ public void applyTo(ConfigurableEnvironment environment) {
+ Properties props = new Properties();
+ props.put("spring.ai.ollama.init.pull-model-strategy", "when_missing");
+ props.put("spring.ai.ollama.chat.model", enableContainerAnnotation.model());
+ props.put(
+ "spring.ai.ollama.chat.options.temperature",
+ enableContainerAnnotation.options().temperature());
+ props.put("spring.ai.ollama.chat.options.topp", enableContainerAnnotation.options().topP());
+
+ environment
+ .getPropertySources()
+ .addFirst(new PropertiesPropertySource("testcontainers", props));
+ }
+}
diff --git a/modules/ollama/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider b/modules/ollama/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider
new file mode 100644
index 0000000..4a429be
--- /dev/null
+++ b/modules/ollama/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider
@@ -0,0 +1 @@
+io.flowinquiry.testcontainers.ai.OllamaContainerProvider
\ No newline at end of file
diff --git a/modules/postgresql/src/main/java/io/flowinquiry/testcontainers/jdbc/postgresql/PostgreSqlContainerProvider.java b/modules/postgresql/src/main/java/io/flowinquiry/testcontainers/jdbc/postgresql/PostgreSqlContainerProvider.java
index 135b652..a24017d 100644
--- a/modules/postgresql/src/main/java/io/flowinquiry/testcontainers/jdbc/postgresql/PostgreSqlContainerProvider.java
+++ b/modules/postgresql/src/main/java/io/flowinquiry/testcontainers/jdbc/postgresql/PostgreSqlContainerProvider.java
@@ -1,61 +1,21 @@
package io.flowinquiry.testcontainers.jdbc.postgresql;
-import io.flowinquiry.testcontainers.jdbc.Rdbms;
+import static io.flowinquiry.testcontainers.ContainerType.POSTGRESQL;
+
+import io.flowinquiry.testcontainers.ContainerType;
import io.flowinquiry.testcontainers.jdbc.SpringAwareJdbcContainerProvider;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.PostgreSQLContainer;
-/**
- * PostgreSQL-specific implementation of the {@link SpringAwareJdbcContainerProvider}.
- *
- * This class provides support for PostgreSQL database containers in the Spring TestContainers
- * framework. It creates and manages a {@link PostgreSQLContainer} instance, which is a
- * TestContainers implementation for PostgreSQL databases.
- *
- * This provider is automatically discovered by the {@link
- * io.flowinquiry.testcontainers.jdbc.JdbcContainerProviderFactory} using Java's ServiceLoader
- * mechanism when a test class is annotated with {@code @EnablePostgreSQL}.
- *
- * The provider handles:
- *
- * This implementation returns {@link Rdbms#POSTGRESQL}, indicating that this provider supports
- * PostgreSQL databases.
- *
- * @return {@link Rdbms#POSTGRESQL} as the supported database type
- */
@Override
- public Rdbms getType() {
- return Rdbms.POSTGRESQL;
+ public ContainerType getContainerType() {
+ return POSTGRESQL;
}
- /**
- * Creates a new PostgreSQL database container.
- *
- * This method creates a {@link PostgreSQLContainer} instance configured with the Docker image
- * and version specified in the {@code @EnablePostgreSQL} annotation on the test class.
- *
- * The container is created but not started. The {@link #start()} method must be called to
- * start the container before it can be used.
- *
- * @return a new {@link PostgreSQLContainer} instance
- */
@Override
- protected JdbcDatabaseContainer> createJdbcDatabaseContainer() {
+ protected JdbcDatabaseContainer> createContainer() {
return new PostgreSQLContainer<>(dockerImage + ":" + version);
}
}
diff --git a/modules/postgresql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider b/modules/postgresql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider
similarity index 100%
rename from modules/postgresql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.jdbc.JdbcContainerProvider
rename to modules/postgresql/src/main/resources/META-INF/services/io.flowinquiry.testcontainers.SpringAwareContainerProvider
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6df8652..0a738c4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,7 +28,9 @@ include("spring-testcontainers")
include("modules")
include("modules:postgresql")
include("modules:mysql")
+include("modules:ollama")
include("examples")
include("examples:springboot-postgresql")
include("examples:spring-postgresql")
include("examples:springboot-mysql")
+include("examples:springboot-ollama")
\ No newline at end of file
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerContextCustomizerFactory.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerContextCustomizerFactory.java
new file mode 100644
index 0000000..177da9a
--- /dev/null
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ContainerContextCustomizerFactory.java
@@ -0,0 +1,42 @@
+package io.flowinquiry.testcontainers;
+
+import java.util.List;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+
+/**
+ * A factory for creating context customizers that integrate TestContainers with Spring test
+ * contexts.
+ *
+ * This class implements Spring's {@link ContextCustomizerFactory} interface to provide
+ * integration between TestContainers and Spring's test framework. It creates customizers that
+ * retrieve container providers from the {@link ContainerRegistry} and apply their configuration to
+ * the Spring test context environment.
+ *
+ * The factory works in conjunction with {@link ContainerLifecycleExtension} which manages the
+ * container lifecycle and registers container providers in the {@link ContainerRegistry}.
+ */
+public class ContainerContextCustomizerFactory implements ContextCustomizerFactory {
+
+ /**
+ * Creates a context customizer for the specified test class.
+ *
+ * The created customizer retrieves the {@link SpringAwareContainerProvider} associated with
+ * the test class from the {@link ContainerRegistry} and applies its configuration to the Spring
+ * environment. This allows test classes to access container-specific properties (like connection
+ * URLs, ports, etc.) through Spring's environment.
+ *
+ * @param testClass the test class for which to create a context customizer
+ * @param configAttributes the context configuration attributes
+ * @return a context customizer that applies container configuration to the Spring environment
+ */
+ @Override
+ public ContextCustomizer createContextCustomizer(
+ Class> testClass, List This utility class maintains a thread-safe mapping between test classes and their
+ * corresponding container providers. It allows for container reuse across test executions and
+ * ensures proper container lifecycle management.
+ *
+ * The registry is used by {@link ContainerLifecycleExtension} to track active containers and
+ * prevent duplicate container creation for the same test class.
+ */
+public final class ContainerRegistry {
+
+ /** Thread-safe map storing the association between test classes and their container providers. */
+ private static final Map This enum defines the different types of containers that can be managed by the framework. Each
+ * container type corresponds to a specific implementation of {@link SpringAwareContainerProvider}
+ * that handles the creation and configuration of that particular container type.
+ *
+ * The container type is used to identify the appropriate provider and to configure Spring
+ * environment properties specific to each container type.
+ */
+public enum ContainerType {
+ /** PostgreSQL database. */
+ POSTGRESQL,
+
+ /** MySQL database. */
+ MYSQL,
+
+ /** Ollama Container */
+ OLLAMA;
+}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/LifecycleAware.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/LifecycleAware.java
new file mode 100644
index 0000000..16f7eda
--- /dev/null
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/LifecycleAware.java
@@ -0,0 +1,31 @@
+package io.flowinquiry.testcontainers;
+
+/**
+ * Interface for components that have a lifecycle with explicit start and stop phases.
+ *
+ * This interface is implemented by classes that need to perform initialization when starting and
+ * cleanup when stopping. It is particularly useful for managing resources that need proper setup
+ * and teardown, such as containers.
+ */
+public interface LifecycleAware {
+
+ /**
+ * Starts the component.
+ *
+ * This method should initialize any resources needed by the component and prepare it for use.
+ * It is typically called during system initialization.
+ *
+ * @throws Exception if an error occurs during startup
+ */
+ void start() throws Exception;
+
+ /**
+ * Stops the component.
+ *
+ * This method should release any resources held by the component and perform necessary
+ * cleanup. It is typically called during system shutdown.
+ *
+ * @throws Exception if an error occurs during shutdown
+ */
+ void stop() throws Exception;
+}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ServiceLoaderContainerFactory.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ServiceLoaderContainerFactory.java
new file mode 100644
index 0000000..7819a30
--- /dev/null
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ServiceLoaderContainerFactory.java
@@ -0,0 +1,50 @@
+package io.flowinquiry.testcontainers;
+
+import java.lang.annotation.Annotation;
+import java.util.ServiceLoader;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Factory class that uses Java's ServiceLoader mechanism to dynamically discover and initialize
+ * container providers.
+ *
+ * This class provides a centralized way to load {@link SpringAwareContainerProvider}
+ * implementations that are registered via the Java ServiceLoader SPI. It allows for dynamic
+ * discovery of container providers without hard-coding dependencies to specific implementations.
+ *
+ * The factory is typically used by container extensions to find an appropriate provider based on
+ * container type and annotation configuration.
+ */
+public class ServiceLoaderContainerFactory {
+
+ /**
+ * Discovers and initializes a container provider that matches the specified filter criteria.
+ *
+ * This method uses Java's ServiceLoader mechanism to find all available {@link
+ * SpringAwareContainerProvider} implementations, filters them according to the provided
+ * predicate, and initializes the first matching provider with the given annotation.
+ *
+ * @param the annotation type that configures the container
+ * @param annotation the annotation instance containing container configuration
+ * @param filter a predicate to select the appropriate provider (e.g., by container type)
+ * @param initializer a function that initializes the provider with the annotation configuration
+ * @return the initialized container provider
+ * @throws IllegalStateException if no matching provider is found
+ */
+ public static SpringAwareContainerProvider getProvider(
+ A annotation,
+ Predicate Implementations of this class should provide specific container creation logic and environment
+ * configuration for different container types (e.g., PostgreSQL, MySQL, Ollama).
+ *
+ * @param This annotation is used to automatically start and stop an Ollama container during test
+ * execution. It leverages the TestContainers library to manage the container lifecycle and is
+ * integrated with Spring's test context framework.
+ *
+ * When applied to a test class, this annotation will:
+ *
+ * Example usage:
+ *
+ * This is a required parameter that determines which AI model will be used for inference in
+ * the tests.
+ *
+ * @return the model identifier/name
+ */
+ String model();
+
+ /**
+ * Configures additional options for the Ollama AI model.
+ *
+ * These options control the behavior of the AI model during inference, such as temperature and
+ * top-p sampling parameters.
+ *
+ * @return the model options configuration
+ * @see OllamaOptions
+ */
+ OllamaOptions options() default @OllamaOptions;
+}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerExtension.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerExtension.java
new file mode 100644
index 0000000..73d1b47
--- /dev/null
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/ai/OllamaContainerExtension.java
@@ -0,0 +1,53 @@
+package io.flowinquiry.testcontainers.ai;
+
+import static io.flowinquiry.testcontainers.ContainerType.OLLAMA;
+import static io.flowinquiry.testcontainers.ServiceLoaderContainerFactory.getProvider;
+
+import io.flowinquiry.testcontainers.ContainerLifecycleExtension;
+import io.flowinquiry.testcontainers.SpringAwareContainerProvider;
+import org.testcontainers.containers.GenericContainer;
+
+/**
+ * JUnit Jupiter extension that manages the lifecycle of Ollama containers for AI testing.
+ *
+ * This extension works with the {@link EnableOllamaContainer} annotation to automatically start
+ * and stop Ollama containers for tests. It handles container initialization based on the
+ * configuration provided in the annotation.
+ *
+ * Usage example:
+ *
+ * This annotation is used in conjunction with {@link EnableOllamaContainer} to configure the
+ * behavior of the AI model during inference.
+ *
+ * Example usage:
+ *
+ * Higher values (e.g., 0.8) make the output more random and creative, while lower values
+ * (e.g., 0.2) make the output more focused and deterministic.
+ *
+ * @return the temperature value as a string, defaults to "0.5"
+ */
+ String temperature() default "0.5";
+
+ /**
+ * Controls the diversity of the model's output through nucleus sampling.
+ *
+ * The model will only consider tokens with a cumulative probability less than topP. A higher
+ * value (e.g., 0.9) will include more low-probability tokens, while a lower value (e.g., 0.5)
+ * will be more selective.
+ *
+ * @return the top-p value as a string, defaults to "0.8"
+ */
+ String topP() default "0.8";
+}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableJdbcContainer.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableJdbcContainer.java
index 4974cdb..5bce83c 100644
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableJdbcContainer.java
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableJdbcContainer.java
@@ -1,9 +1,14 @@
package io.flowinquiry.testcontainers.jdbc;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import io.flowinquiry.testcontainers.ContainerContextCustomizerFactory;
+import io.flowinquiry.testcontainers.ContainerRegistry;
+import io.flowinquiry.testcontainers.ContainerType;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -15,18 +20,18 @@
* EnableMySQL} which are themselves annotated with this meta-annotation.
*
* When a test class is annotated with a database-specific annotation (e.g.,
- * {@code @EnablePostgreSQL}), the {@link JdbcExtension} will automatically:
+ * {@code @EnablePostgreSQL}), the {@link JdbcContainerExtension} will automatically:
*
* When used in conjunction with Spring Boot tests, the container's connection details will be
* automatically configured in the Spring environment through the {@link
- * JdbcContainerContextCustomizerFactory}.
+ * ContainerContextCustomizerFactory}.
*
* To create a new database-specific annotation, use this annotation as a meta-annotation:
*
@@ -41,28 +46,28 @@
* }
* }
*
- * @see JdbcExtension
- * @see JdbcContainerProvider
- * @see JdbcContainerContextCustomizerFactory
- * @see Rdbms
+ * @see JdbcContainerExtension
+ * @see ContainerContextCustomizerFactory
+ * @see ContainerType
* @see EnablePostgreSQL
* @see EnableMySQL
*/
-@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
+@Target({ANNOTATION_TYPE, TYPE})
+@Retention(RUNTIME)
@Documented
-@ExtendWith(JdbcExtension.class)
+@ExtendWith(JdbcContainerExtension.class)
public @interface EnableJdbcContainer {
/**
* Specifies the type of relational database management system to use.
*
- * This attribute is required and must be one of the values defined in the {@link Rdbms} enum.
- * Currently supported values are {@link Rdbms#POSTGRESQL} and {@link Rdbms#MYSQL}.
+ * This attribute is required and must be one of the values defined in the {@link
+ * ContainerType} enum. Currently supported values are {@link ContainerType#POSTGRESQL} and {@link
+ * ContainerType#MYSQL}.
*
* @return the RDBMS type to use
*/
- Rdbms rdbms();
+ ContainerType rdbms();
/**
* Specifies the Docker image to use for the database container.
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableMySQL.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableMySQL.java
index 63dcafd..267f5c3 100644
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableMySQL.java
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnableMySQL.java
@@ -1,49 +1,18 @@
package io.flowinquiry.testcontainers.jdbc;
+import static io.flowinquiry.testcontainers.ContainerType.MYSQL;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/**
- * Annotation for enabling MySQL database containers in tests.
- *
- * When a test class is annotated with {@code @EnableMySQL}, a MySQL container will be
- * automatically started before the tests run and stopped after they complete.
- *
- * Example usage:
- *
- * You can customize the MySQL version and Docker image:
- *
- * When used with Spring Boot tests, the MySQL container's connection details will be
- * automatically configured in the Spring environment, making them available to your application and
- * tests.
- *
- * @see EnableJdbcContainer
- * @see JdbcExtension
- * @see Rdbms#MYSQL
- */
-@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
+@Target({ANNOTATION_TYPE, TYPE})
+@Retention(RUNTIME)
@Documented
-@EnableJdbcContainer(rdbms = Rdbms.MYSQL)
+@EnableJdbcContainer(rdbms = MYSQL)
public @interface EnableMySQL {
/**
* Specifies the version of the MySQL Docker image to use.
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnablePostgreSQL.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnablePostgreSQL.java
index 6a3c6ec..7f54b3b 100644
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnablePostgreSQL.java
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/EnablePostgreSQL.java
@@ -1,9 +1,13 @@
package io.flowinquiry.testcontainers.jdbc;
+import static io.flowinquiry.testcontainers.ContainerType.POSTGRESQL;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import io.flowinquiry.testcontainers.ContainerType;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
@@ -38,13 +42,13 @@
* tests.
*
* @see EnableJdbcContainer
- * @see JdbcExtension
- * @see Rdbms#POSTGRESQL
+ * @see JdbcContainerExtension
+ * @see ContainerType#POSTGRESQL
*/
-@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
+@Target({ANNOTATION_TYPE, TYPE})
+@Retention(RUNTIME)
@Documented
-@EnableJdbcContainer(rdbms = Rdbms.POSTGRESQL)
+@EnableJdbcContainer(rdbms = POSTGRESQL)
public @interface EnablePostgreSQL {
/**
* Specifies the version of the PostgreSQL Docker image to use.
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerContextCustomizerFactory.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerContextCustomizerFactory.java
deleted file mode 100644
index 6501c29..0000000
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerContextCustomizerFactory.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package io.flowinquiry.testcontainers.jdbc;
-
-import java.util.List;
-import org.springframework.test.context.ContextConfigurationAttributes;
-import org.springframework.test.context.ContextCustomizer;
-import org.springframework.test.context.ContextCustomizerFactory;
-
-/**
- * Spring {@link ContextCustomizerFactory} that integrates TestContainers JDBC containers with
- * Spring's test context.
- *
- * This factory is automatically discovered by Spring's SPI mechanism through the {@code
- * META-INF/spring.factories} file. When a Spring test context is being prepared, Spring will call
- * this factory to create a {@link ContextCustomizer} for each test class.
- *
- * The customizer created by this factory:
- *
- * This enables seamless integration between TestContainers database containers and Spring Boot
- * tests. When a test class is annotated with both {@code @SpringBootTest} and a database-specific
- * annotation like {@link EnablePostgreSQL} or {@link EnableMySQL}, the database connection details
- * from the TestContainers container will be automatically configured in the Spring environment,
- * making them available to your application and tests.
- *
- * Example of how this works in a typical test:
- *
- * This method is called by Spring's test context framework for each test class that uses
- * Spring's testing support. The customizer it returns is responsible for applying the database
- * container's connection details to the Spring environment.
- *
- * The customizer:
- *
- * This method is automatically called by Spring's test context framework and should not be
- * called directly.
- *
- * @param testClass the test class for which to create a context customizer
- * @param configAttributes the context configuration attributes for the test class
- * @return a {@link ContextCustomizer} that configures the Spring environment with the database
- * container's connection details, or a no-op customizer if no container is associated with
- * the test class
- */
- @Override
- public ContextCustomizer createContextCustomizer(
- Class> testClass, List This interface defines the contract for classes that create and manage database containers
- * using Testcontainers. Implementations of this interface are responsible for:
- *
- * This interface serves as a wrapper around TestContainers' database containers ({@link
- * org.testcontainers.containers.JdbcDatabaseContainer}), providing a standardized way to create,
- * configure, and manage these containers within the testing framework. Concrete implementations
- * like {@link SpringAwareJdbcContainerProvider} add additional functionality such as Spring
- * environment integration.
- *
- * Implementations of this interface are discovered using Java's {@link java.util.ServiceLoader}
- * mechanism and are selected based on the database type specified in the {@link
- * EnableJdbcContainer} annotation.
- *
- * The lifecycle of a JdbcContainerProvider is typically managed by the {@link JdbcExtension}
- * JUnit extension, which creates the provider, starts the container before tests run, and stops it
- * after tests complete.
- *
- * @see EnableJdbcContainer
- * @see JdbcExtension
- * @see JdbcContainerProviderFactory
- * @see SpringAwareJdbcContainerProvider
- * @see Rdbms
- * @see org.testcontainers.containers.JdbcDatabaseContainer
- */
-public interface JdbcContainerProvider {
-
- /**
- * Returns the type of relational database management system this provider supports.
- *
- * This method is used by the {@link JdbcContainerProviderFactory} to select the appropriate
- * provider based on the database type specified in the {@link EnableJdbcContainer} annotation.
- *
- * @return the RDBMS type supported by this provider
- */
- Rdbms getType();
-
- /**
- * Starts the database container.
- *
- * This method is called by the {@link JdbcExtension} before tests run. Implementations should
- * create and start the container, making it ready for use by tests.
- */
- void start();
-
- /**
- * Stops the database container.
- *
- * This method is called by the {@link JdbcExtension} after tests complete. Implementations
- * should stop and clean up the container to free resources.
- */
- void stop();
-}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerProviderFactory.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerProviderFactory.java
deleted file mode 100644
index b1b72f5..0000000
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerProviderFactory.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package io.flowinquiry.testcontainers.jdbc;
-
-import java.util.ServiceLoader;
-
-/**
- * Factory class for creating and configuring JDBC container providers.
- *
- * This factory is responsible for discovering and initializing the appropriate {@link
- * JdbcContainerProvider} implementation based on the database type specified in the {@link
- * EnableJdbcContainer} annotation.
- *
- * The factory uses Java's {@link ServiceLoader} mechanism to discover available provider
- * implementations. It selects the provider that matches the requested database type, initializes it
- * with the specified Docker image and version, and prepares it for use in tests.
- *
- * This class is primarily used by the {@link JdbcExtension} to create container providers for
- * test classes annotated with database-specific annotations like {@code @EnablePostgreSQL} or
- * {@code @EnableMySQL}.
- *
- * @see JdbcContainerProvider
- * @see EnableJdbcContainer
- * @see ServiceLoader
- */
-public class JdbcContainerProviderFactory {
-
- /**
- * Creates and initializes a JDBC container provider for the specified configuration.
- *
- * This method:
- *
- * This class serves as a central registry that maintains a mapping between test classes and
- * their corresponding {@link JdbcContainerProvider} instances. It ensures that each test class has
- * access to its own database container provider, and prevents duplicate container creation when a
- * test class is used in multiple contexts.
- *
- * The registry is primarily used by the {@link JdbcExtension} to:
- *
- * This registry is thread-safe and uses a {@link ConcurrentHashMap} to store the mappings,
- * allowing it to be safely used in concurrent test execution environments.
- *
- * Example usage within the framework:
- *
- * This method registers a {@link JdbcContainerProvider} for a specific test class, making it
- * available for retrieval later. If a provider is already registered for the given test class, it
- * will be replaced with the new provider.
- *
- * This method is typically called by the {@link JdbcExtension} when it creates a new provider
- * for a test class that doesn't already have one.
- *
- * @param testClass the test class to associate with the provider
- * @param provider the JDBC container provider to register
- */
- public static void set(Class> testClass, JdbcContainerProvider provider) {
- providers.put(testClass, provider);
- }
-
- /**
- * Retrieves the provider associated with the given test class.
- *
- * This method returns the {@link JdbcContainerProvider} that was previously registered for the
- * specified test class using the {@link #set(Class, JdbcContainerProvider)} method. If no
- * provider has been registered for the test class, this method returns {@code null}.
- *
- * This method is typically called by the {@link JdbcExtension} to check if a provider already
- * exists for a test class before creating a new one.
- *
- * @param testClass the test class whose provider to retrieve
- * @return the JDBC container provider associated with the test class, or {@code null} if none
- * exists
- */
- public static JdbcContainerProvider get(Class> testClass) {
- return providers.get(testClass);
- }
-
- /**
- * Checks if a provider has already been registered for the given test class.
- *
- * This method returns {@code true} if a {@link JdbcContainerProvider} has been registered for
- * the specified test class using the {@link #set(Class, JdbcContainerProvider)} method, and
- * {@code false} otherwise.
- *
- * This method is typically called by the {@link JdbcExtension} to determine whether to create
- * a new provider or reuse an existing one.
- *
- * @param testClass the test class to check
- * @return {@code true} if a provider exists for the test class, {@code false} otherwise
- */
- public static boolean contains(Class> testClass) {
- return providers.containsKey(testClass);
- }
-
- /**
- * Clears the provider for a given test class.
- *
- * This method removes the {@link JdbcContainerProvider} that was previously registered for the
- * specified test class. If no provider has been registered for the test class, this method has no
- * effect.
- *
- * This method is typically called by the {@link JdbcExtension} after tests complete to clean
- * up resources and prevent memory leaks.
- *
- * @param testClass the test class whose provider to clear
- */
- public static void clear(Class> testClass) {
- providers.remove(testClass);
- }
-
- /**
- * Clears all registered providers.
- *
- * This method removes all {@link JdbcContainerProvider} instances that have been registered
- * with this registry. It is useful for cleaning up all resources at once, such as during testing
- * or when the application is shutting down.
- *
- * This method should be used with caution, as it affects all registered providers, potentially
- * impacting other tests that may be running concurrently.
- */
- public static void clearAll() {
- providers.clear();
- }
-}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcExtension.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcExtension.java
deleted file mode 100644
index 9f36b1c..0000000
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcExtension.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package io.flowinquiry.testcontainers.jdbc;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.HashSet;
-import java.util.Set;
-import org.junit.jupiter.api.extension.AfterAllCallback;
-import org.junit.jupiter.api.extension.BeforeAllCallback;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A JUnit 5 extension that manages JDBC database containers for integration testing.
- *
- * This extension automatically starts and stops database containers based on annotations present
- * on the test class. It works with meta-annotations that are themselves annotated with {@link
- * EnableJdbcContainer}, such as {@code @EnablePostgreSQL}, {@code @EnableMySQL}, etc.
- *
- * The extension handles the lifecycle of database containers:
- *
- * Usage example:
- *
- * This method detects the database configuration from annotations on the test class, creates
- * the appropriate container provider, and starts the database container if needed. If a container
- * for this test class is already running, it reuses the existing container.
- *
- * @param context the extension context for the test class
- */
- @Override
- public void beforeAll(ExtensionContext context) {
- Class> testClass = context.getRequiredTestClass();
- EnableJdbcContainer config = resolveJdbcConfig(testClass);
-
- if (config == null) return;
-
- if (JdbcContainerRegistry.contains(testClass)) {
- this.provider = JdbcContainerRegistry.get(testClass);
- } else {
- this.provider = JdbcContainerProviderFactory.getProvider(config);
- log.debug("Starting JDBC container {} for test class: {}", provider, testClass.getName());
- provider.start();
- JdbcContainerRegistry.set(testClass, provider);
- }
- }
-
- /**
- * Called after all tests in the current test class have completed.
- *
- * This method stops the database container if it was started by this extension and removes the
- * container reference from the registry to allow proper cleanup.
- *
- * @param context the extension context for the test class
- */
- @Override
- public void afterAll(ExtensionContext context) {
- if (provider != null) {
- provider.stop();
- JdbcContainerRegistry.clear(context.getRequiredTestClass());
- log.debug(
- "Stopped JDBC container {} for test class: {}",
- provider,
- context.getRequiredTestClass().getName());
- }
- }
-
- private EnableJdbcContainer resolveJdbcConfig(Class> testClass) {
- if (testClass.isAnnotationPresent(EnableJdbcContainer.class)) {
- throw new IllegalStateException(
- """
- @EnableJdbcContainer is a meta-annotation and should not be used directly.
- Use @EnablePostgreSQL, @EnableMySQL, etc. instead.
- """);
- }
-
- for (Annotation annotation : testClass.getAnnotations()) {
- Annotation jdbcAnnotation =
- findNearestAnnotationWith(annotation, EnableJdbcContainer.class, new HashSet<>());
- if (jdbcAnnotation != null) {
- EnableJdbcContainer meta =
- jdbcAnnotation.annotationType().getAnnotation(EnableJdbcContainer.class);
- return buildResolvedJdbcConfig(jdbcAnnotation, meta);
- }
- }
-
- return null;
- }
-
- private Annotation findNearestAnnotationWith(
- Annotation candidate, Class extends Annotation> target, Set This method extracts configuration values (version and dockerImage) from the source
- * annotation and combines them with the database type (rdbms) from the meta-annotation to create
- * a complete {@link EnableJdbcContainer} configuration.
- *
- * @param sourceAnnotation the source annotation containing version and dockerImage values
- * @param meta the meta-annotation containing the database type (rdbms)
- * @return a resolved {@link EnableJdbcContainer} configuration
- * @throws IllegalStateException if reflection fails to extract values from the source annotation
- */
- private EnableJdbcContainer buildResolvedJdbcConfig(
- Annotation sourceAnnotation, EnableJdbcContainer meta) {
- try {
- Method versionMethod = sourceAnnotation.annotationType().getMethod("version");
- Method imageMethod = sourceAnnotation.annotationType().getMethod("dockerImage");
-
- String version = (String) versionMethod.invoke(sourceAnnotation);
- String dockerImage = (String) imageMethod.invoke(sourceAnnotation);
-
- return new EnableJdbcContainer() {
- @Override
- public Rdbms rdbms() {
- return meta.rdbms();
- }
-
- @Override
- public String version() {
- return version;
- }
-
- @Override
- public String dockerImage() {
- return dockerImage;
- }
-
- @Override
- public Class extends Annotation> annotationType() {
- return EnableJdbcContainer.class;
- }
- };
-
- } catch (ReflectiveOperationException e) {
- throw new IllegalStateException(
- "Failed to extract JDBC container config from annotation: "
- + sourceAnnotation.annotationType().getName(),
- e);
- }
- }
-}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/Rdbms.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/Rdbms.java
deleted file mode 100644
index 2a31809..0000000
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/Rdbms.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.flowinquiry.testcontainers.jdbc;
-
-/**
- * Enumeration of supported relational database management systems.
- *
- * This enum defines the database types that can be used with the testcontainers extension. It is
- * used in the {@link EnableJdbcContainer} annotation to specify which database type to use for
- * testing.
- *
- * Currently supported database types are:
- *
- * This class extends the basic {@link JdbcContainerProvider} interface with Spring-specific
- * functionality, allowing database containers to be seamlessly integrated with Spring's environment
- * and configuration system. It manages a {@link JdbcDatabaseContainer} instance and provides
- * methods to:
+ * This class extends {@link SpringAwareContainerProvider} specifically for JDBC database
+ * containers, providing automatic configuration of Spring datasource properties based on the
+ * container's connection details.
*
- * When a JDBC container is started, this provider automatically configures the Spring
+ * environment with the appropriate datasource URL, username, and password properties from the
+ * container.
*
- * This class is designed to be extended by concrete database-specific implementations such as
- * PostgreSQL or MySQL providers. These implementations must override the {@link
- * #createJdbcDatabaseContainer()} method to create the appropriate container type.
+ * Concrete implementations of this class should provide specific container creation logic for
+ * different database types (e.g., PostgreSQL, MySQL).
*
- * The Spring integration is handled through the {@link #applyTo(ConfigurableEnvironment)}
- * method, which adds the container's connection details (URL, username, password) to the Spring
- * environment as properties. This allows Spring Boot applications to automatically connect to the
- * database container without any additional configuration.
- *
- * This class is used by the {@link JdbcContainerContextCustomizerFactory} to configure the
- * Spring environment for test classes annotated with database-specific annotations like {@link
- * EnablePostgreSQL} or {@link EnableMySQL}.
- *
- * @see JdbcContainerProvider
+ * @param This method is called by the {@link JdbcContainerProviderFactory} when creating a provider
- * for a test class. It sets the Docker image and version to use for the container, and then
- * creates the container by calling {@link #createJdbcDatabaseContainer()}.
- *
- * @param dockerImage the Docker image name to use for the container
- * @param version the version of the Docker image to use
- */
- void initContainerInstance(String dockerImage, String version) {
- this.version = version;
- this.dockerImage = dockerImage;
- log.info("Initializing JDBC container with image {}:{}", dockerImage, version);
- jdbcDatabaseContainer = createJdbcDatabaseContainer();
- }
-
- /**
- * Creates a new JDBC database container of the appropriate type.
- *
- * This method must be implemented by concrete subclasses to create the specific type of
- * database container they support (e.g., PostgreSQL, MySQL). The implementation should use the
- * {@link #dockerImage} and {@link #version} fields to configure the container.
- *
- * @return a new JDBC database container of the appropriate type
- */
- protected abstract JdbcDatabaseContainer> createJdbcDatabaseContainer();
-
- /**
- * Starts the database container.
- *
- * This method is called by the {@link JdbcExtension} before tests run. It delegates to the
- * {@link JdbcDatabaseContainer#start()} method to start the container.
- */
- @Override
- public void start() {
- jdbcDatabaseContainer.start();
- }
-
- /**
- * Stops the database container.
+ * Applies JDBC container configuration to the Spring environment.
*
- * This method is called by the {@link JdbcExtension} after tests complete. It delegates to the
- * {@link JdbcDatabaseContainer#stop()} method to stop the container and free resources.
+ * @param environment the Spring environment to configure with datasource properties
*/
@Override
- public void stop() {
- jdbcDatabaseContainer.stop();
- }
-
- /**
- * Applies the container's connection details to the Spring environment.
- *
- * This method is called by the {@link JdbcContainerContextCustomizerFactory} when configuring
- * the Spring environment for a test class. It adds the container's JDBC URL, username, and
- * password as properties in the Spring environment, allowing Spring Boot applications to
- * automatically connect to the database container.
- *
- * @param environment the Spring environment to configure
- */
- void applyTo(ConfigurableEnvironment environment) {
+ public final void applyTo(ConfigurableEnvironment environment) {
Properties props = new Properties();
- props.put("spring.datasource.url", jdbcDatabaseContainer.getJdbcUrl());
- props.put("spring.datasource.username", jdbcDatabaseContainer.getUsername());
- props.put("spring.datasource.password", jdbcDatabaseContainer.getPassword());
+ props.put("spring.datasource.url", container.getJdbcUrl());
+ props.put("spring.datasource.username", container.getUsername());
+ props.put("spring.datasource.password", container.getPassword());
- log.debug("Database container url: {}", jdbcDatabaseContainer.getJdbcUrl());
+ log.debug("Database container url: {}", container.getJdbcUrl());
environment
.getPropertySources()
diff --git a/spring-testcontainers/src/main/resources/META-INF/spring.factories b/spring-testcontainers/src/main/resources/META-INF/spring.factories
index b67a15a..60e9524 100644
--- a/spring-testcontainers/src/main/resources/META-INF/spring.factories
+++ b/spring-testcontainers/src/main/resources/META-INF/spring.factories
@@ -1 +1 @@
-org.springframework.test.context.ContextCustomizerFactory=io.flowinquiry.testcontainers.jdbc.JdbcContainerContextCustomizerFactory
\ No newline at end of file
+org.springframework.test.context.ContextCustomizerFactory=io.flowinquiry.testcontainers.ContainerContextCustomizerFactory
\ No newline at end of file
From 0b65ea3adc0996e19f6f3e5ff03eb18d09ed3e14 Mon Sep 17 00:00:00 2001
From: Hai Phuc Nguyen <“3423575+haiphucnguyen@users.noreply.github.com”>
Date: Fri, 13 Jun 2025 23:04:25 -0700
Subject: [PATCH 2/2] Update
---
.../testcontainers/examples/ollama/OllamaDemoAppTest.java | 2 +-
examples/springboot-ollama/src/test/resources/logback.xml | 4 ----
.../testcontainers/ai/OllamaContainerProvider.java | 1 +
3 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/examples/springboot-ollama/src/test/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoAppTest.java b/examples/springboot-ollama/src/test/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoAppTest.java
index 9ef47f9..e6445bd 100644
--- a/examples/springboot-ollama/src/test/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoAppTest.java
+++ b/examples/springboot-ollama/src/test/java/io/flowinquiry/testcontainers/examples/ollama/OllamaDemoAppTest.java
@@ -22,7 +22,7 @@
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableOllamaContainer(
dockerImage = "ollama/ollama",
- version = "0.1.26",
+ version = "0.9.0",
model = "smollm2:135m",
options = @OllamaOptions(temperature = "0.7", topP = "0.5"))
@ActiveProfiles("test")
diff --git a/examples/springboot-ollama/src/test/resources/logback.xml b/examples/springboot-ollama/src/test/resources/logback.xml
index 3ba90da..06012ac 100644
--- a/examples/springboot-ollama/src/test/resources/logback.xml
+++ b/examples/springboot-ollama/src/test/resources/logback.xml
@@ -16,10 +16,6 @@
- *
- *
- * @see SpringAwareJdbcContainerProvider
- * @see PostgreSQLContainer
- * @see io.flowinquiry.testcontainers.jdbc.EnablePostgreSQL
- */
-public class PostgreSqlContainerProvider extends SpringAwareJdbcContainerProvider {
+public final class PostgreSqlContainerProvider extends SpringAwareJdbcContainerProvider {
- /**
- * Returns the type of database managed by this provider.
- *
- *
+ *
+ *
+ *
+ * @SpringBootTest
+ * @EnableOllamaContainer(
+ * dockerImage = "ollama/ollama",
+ * version = "0.1.26",
+ * model = "hf.co/microsoft/Phi-3-mini-4k-instruct-gguf"
+ * )
+ * public class MyAiTest {
+ * // Test methods
+ * }
+ *
+ *
+ * @see OllamaContainerExtension
+ * @see OllamaOptions
+ */
+@Target({ANNOTATION_TYPE, TYPE})
+@Retention(RUNTIME)
+@Documented
+@ExtendWith(OllamaContainerExtension.class)
+public @interface EnableOllamaContainer {
+
+ /**
+ * Specifies the version of the Ollama container to use.
+ *
+ * @return the container version, defaults to "latest"
+ */
+ String version() default "latest";
+
+ /**
+ * Specifies the Docker image to use for the Ollama container.
+ *
+ * @return the Docker image name, defaults to "ollama"
+ */
+ String dockerImage() default "ollama";
+
+ /**
+ * Specifies the AI model to load in the Ollama container.
+ *
+ *
+ * @EnableOllamaContainer(dockerImage = "ollama/ollama", version = "latest")
+ * public class OllamaTest {
+ * // Test methods
+ * }
+ *
+ */
+public class OllamaContainerExtension extends ContainerLifecycleExtension
+ * @EnableOllamaContainer(
+ * model = "llama2",
+ * options = @OllamaOptions(
+ * temperature = "0.7",
+ * topP = "0.9"
+ * )
+ * )
+ *
+ *
+ * @see EnableOllamaContainer
+ */
+@Retention(RUNTIME)
+public @interface OllamaOptions {
+
+ /**
+ * Controls the randomness of the model's output.
+ *
+ *
*
*
* {@code
- * @SpringBootTest
- * @EnableMySQL
- * public class MyDatabaseTest {
- * // Test methods...
- * }
- * }
- *
- * {@code
- * @SpringBootTest
- * @EnableMySQL(version = "8.0.32", dockerImage = "mysql")
- * public class MyCustomMySQLTest {
- * // Test methods...
- * }
- * }
- *
- *
- *
- *
- * {@code
- * @SpringBootTest
- * @EnablePostgreSQL
- * public class MyDatabaseTest {
- * // The JdbcContainerContextCustomizerFactory will automatically configure
- * // spring.datasource.url, spring.datasource.username, and spring.datasource.password
- * // in the Spring environment, based on the PostgreSQL container's connection details.
- *
- * @Autowired
- * private DataSource dataSource; // This will be connected to the PostgreSQL container
- *
- * // Test methods...
- * }
- * }
- *
- * @see JdbcContainerProvider
- * @see SpringAwareJdbcContainerProvider
- * @see JdbcContainerRegistry
- * @see EnableJdbcContainer
- * @see EnablePostgreSQL
- * @see EnableMySQL
- */
-public class JdbcContainerContextCustomizerFactory implements ContextCustomizerFactory {
-
- /**
- * Creates a {@link ContextCustomizer} for the specified test class.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * @param enableJdbcContainer the annotation containing the database configuration
- * @return a fully initialized JDBC container provider ready for use in tests
- * @throws IllegalStateException if no provider is found for the specified database type
- */
- public static JdbcContainerProvider getProvider(EnableJdbcContainer enableJdbcContainer) {
- SpringAwareJdbcContainerProvider provider =
- (SpringAwareJdbcContainerProvider)
- ServiceLoader.load(JdbcContainerProvider.class).stream()
- .map(ServiceLoader.Provider::get)
- .filter(p -> p.getType() == enableJdbcContainer.rdbms())
- .findFirst()
- .orElseThrow(
- () ->
- new IllegalStateException(
- "No provider found for " + enableJdbcContainer.rdbms()));
-
- provider.initContainerInstance(
- enableJdbcContainer.dockerImage(), enableJdbcContainer.version());
- provider.createJdbcDatabaseContainer();
- return provider;
- }
-}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerRegistry.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerRegistry.java
deleted file mode 100644
index 084b35c..0000000
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/JdbcContainerRegistry.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package io.flowinquiry.testcontainers.jdbc;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Registry for managing JDBC container providers associated with test classes.
- *
- *
- *
- *
- * {@code
- * // In JdbcExtension.beforeAll:
- * if (JdbcContainerRegistry.contains(testClass)) {
- * // Reuse existing provider
- * provider = JdbcContainerRegistry.get(testClass);
- * } else {
- * // Create new provider
- * provider = JdbcContainerProviderFactory.getProvider(config);
- * provider.start();
- * JdbcContainerRegistry.set(testClass, provider);
- * }
- *
- * // In JdbcExtension.afterAll:
- * provider.stop();
- * JdbcContainerRegistry.clear(testClass);
- * }
- *
- * @see JdbcContainerProvider
- * @see JdbcExtension
- * @see EnableJdbcContainer
- */
-public final class JdbcContainerRegistry {
-
- private static final Map
- *
- *
- * {@code
- * @EnablePostgreSQL
- * class MyIntegrationTest {
- * // Test methods that require a PostgreSQL database
- * }
- * }
- */
-public class JdbcExtension implements BeforeAllCallback, AfterAllCallback {
-
- private static final Logger log = LoggerFactory.getLogger(JdbcExtension.class);
-
- private JdbcContainerProvider provider;
-
- /**
- * Called before all tests in the current test class.
- *
- *
- *
- *
- * @see EnableJdbcContainer
- * @see JdbcContainerProvider
- * @see JdbcExtension
- */
-public enum Rdbms {
- /** PostgreSQL database. */
- POSTGRESQL,
-
- /** MySQL database. */
- MYSQL
-}
diff --git a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/SpringAwareJdbcContainerProvider.java b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/SpringAwareJdbcContainerProvider.java
index 79d6c2a..ef45b36 100644
--- a/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/SpringAwareJdbcContainerProvider.java
+++ b/spring-testcontainers/src/main/java/io/flowinquiry/testcontainers/jdbc/SpringAwareJdbcContainerProvider.java
@@ -1,5 +1,6 @@
package io.flowinquiry.testcontainers.jdbc;
+import io.flowinquiry.testcontainers.SpringAwareContainerProvider;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -8,137 +9,44 @@
import org.testcontainers.containers.JdbcDatabaseContainer;
/**
- * Abstract base class for JDBC container providers that integrate with Spring.
+ * Abstract base class for JDBC database container providers that integrate with Spring.
*
- *
- *
+ *