Skip to content

Commit

Permalink
Add container support for Oracle Free which replaces Oracle XE
Browse files Browse the repository at this point in the history
Update Docker Compose and Testcontainers support to work with
`gvenzl/oracle-free` which replaces `gvenzl/oracle-xe`.

Closes gh-38476
  • Loading branch information
philwebb committed Nov 21, 2023
1 parent 0897d75 commit 6c3dec4
Show file tree
Hide file tree
Showing 18 changed files with 343 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,33 @@

package org.springframework.boot.docker.compose.service.connection;

import java.util.Arrays;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.springframework.boot.docker.compose.core.ImageReference;
import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.util.Assert;

/**
* {@link Predicate} that matches against connection names.
* {@link Predicate} that matches against connection name.
*
* @author Phillip Webb
*/
class ConnectionNamePredicate implements Predicate<DockerComposeConnectionSource> {

private final String required;
private final Set<String> required;

ConnectionNamePredicate(String required) {
this.required = asCanonicalName(required);
ConnectionNamePredicate(String... required) {
Assert.notEmpty(required, "Required must not be empty");
this.required = Arrays.stream(required).map(this::asCanonicalName).collect(Collectors.toSet());
}

@Override
public boolean test(DockerComposeConnectionSource source) {
String actual = getActual(source.getRunningService());
return this.required.equals(actual);
return this.required.contains(actual);
}

private String getActual(RunningService service) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ protected DockerComposeConnectionDetailsFactory(String connectionName, String...
this(new ConnectionNamePredicate(connectionName), requiredClassNames);
}

/**
* Create a new {@link DockerComposeConnectionDetailsFactory} instance.
* @param connectionNames the required connection name
* @param requiredClassNames the names of classes that must be present
* @since 3.2.0
*/
protected DockerComposeConnectionDetailsFactory(String[] connectionNames, String... requiredClassNames) {
this(new ConnectionNamePredicate(connectionNames), requiredClassNames);
}

/**
* Create a new {@link DockerComposeConnectionDetailsFactory} instance.
* @param predicate a predicate used to check when a service is accepted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
*/
class OracleEnvironment {

static final String[] CONTAINER_NAMES = { "gvenzl/oracle-xe", "gvenzl/oracle-free" };

private final String username;

private final String password;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class OracleJdbcDockerComposeConnectionDetailsFactory
extends DockerComposeConnectionDetailsFactory<JdbcConnectionDetails> {

protected OracleJdbcDockerComposeConnectionDetailsFactory() {
super("gvenzl/oracle-xe");
super(OracleEnvironment.CONTAINER_NAMES);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class OracleR2dbcDockerComposeConnectionDetailsFactory
extends DockerComposeConnectionDetailsFactory<R2dbcConnectionDetails> {

OracleR2dbcDockerComposeConnectionDetailsFactory() {
super("gvenzl/oracle-xe", "io.r2dbc.spi.ConnectionFactoryOptions");
super(OracleEnvironment.CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@ void labeled() {
.accepts(sourceOf("internalhost:8080/libs/libs/mzipkin", "openzipkin/zipkin"));
}

private Predicate<DockerComposeConnectionSource> predicateOf(String required) {
@Test
void multiple() {
assertThat(predicateOf("elasticsearch1", "elasticsearch2")).accepts(sourceOf("elasticsearch1"))
.accepts(sourceOf("elasticsearch2"));

}

private Predicate<DockerComposeConnectionSource> predicateOf(String... required) {
return new ConnectionNamePredicate(required);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.docker.compose.service.connection.oracle;

import java.sql.Driver;
import java.time.Duration;

import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;

import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.testsupport.junit.DisabledOnOs;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.ClassUtils;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Integration tests for {@link OracleJdbcDockerComposeConnectionDetailsFactory}
*
* @author Andy Wilkinson
*/
@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64",
disabledReason = "The Oracle image has no ARM support")
class OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests
extends AbstractDockerComposeIntegrationTests {

OracleFreeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
super("oracle-compose.yaml", DockerImageNames.oracleFree());
}

@Test
@SuppressWarnings("unchecked")
void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() throws Exception {
JdbcConnectionDetails connectionDetails = run(JdbcConnectionDetails.class);
assertThat(connectionDetails.getUsername()).isEqualTo("app_user");
assertThat(connectionDetails.getPassword()).isEqualTo("app_user_secret");
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:oracle:thin:@").endsWith("/xepdb1");
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClass((Class<? extends Driver>) ClassUtils.forName(connectionDetails.getDriverClassName(),
getClass().getClassLoader()));
Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> {
JdbcTemplate template = new JdbcTemplate(dataSource);
assertThat(template.queryForObject(DatabaseDriver.ORACLE.getValidationQuery(), String.class))
.isEqualTo("Hello");
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.docker.compose.service.connection.oracle;

import java.time.Duration;

import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;

import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails;
import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.testsupport.junit.DisabledOnOs;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.r2dbc.core.DatabaseClient;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Integration tests for {@link OracleR2dbcDockerComposeConnectionDetailsFactory}
*
* @author Andy Wilkinson
*/
@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64",
disabledReason = "The Oracle image has no ARM support")
class OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests
extends AbstractDockerComposeIntegrationTests {

OracleFreeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
super("oracle-compose.yaml", DockerImageNames.oracleFree());
}

@Test
void runCreatesConnectionDetailsThatCanBeUsedToAccessDatabase() {
R2dbcConnectionDetails connectionDetails = run(R2dbcConnectionDetails.class);
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions();
assertThat(connectionFactoryOptions.toString()).contains("database=xepdb1", "driver=oracle",
"password=REDACTED", "user=app_user");
assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD))
.isEqualTo("app_user_secret");
Awaitility.await().atMost(Duration.ofMinutes(1)).ignoreExceptions().untilAsserted(() -> {
Object result = DatabaseClient.create(ConnectionFactories.get(connectionFactoryOptions))
.sql(DatabaseDriver.ORACLE.getValidationQuery())
.map((row, metadata) -> row.get(0))
.first()
.block(Duration.ofSeconds(30));
assertThat(result).isEqualTo("Hello");
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
*/
@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64",
disabledReason = "The Oracle image has no ARM support")
class OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests {
class OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests {

OracleJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
OracleXeJdbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
super("oracle-compose.yaml", DockerImageNames.oracleXe());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
*/
@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64",
disabledReason = "The Oracle image has no ARM support")
class OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests {
class OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests {

OracleR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
OracleXeR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests() {
super("oracle-compose.yaml", DockerImageNames.oracleXe());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The following service connections are currently supported:
| Containers named "elasticsearch"

| `JdbcConnectionDetails`
| Containers named "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres"
| Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres"

| `MongoConnectionDetails`
| Containers named "mongo"
Expand All @@ -92,7 +92,7 @@ The following service connections are currently supported:
| Containers named "apachepulsar/pulsar"

| `R2dbcConnectionDetails`
| Containers named "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres"
| Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres"

| `RabbitConnectionDetails`
| Containers named "rabbitmq"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
optional("org.testcontainers:mysql")
optional("org.testcontainers:neo4j")
optional("org.testcontainers:oracle-xe")
optional("org.testcontainers:oracle-free")
optional("org.testcontainers:postgresql")
optional("org.testcontainers:pulsar")
optional("org.testcontainers:rabbitmq")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.testcontainers.service.connection.r2dbc;

import io.r2dbc.spi.ConnectionFactoryOptions;
import org.testcontainers.oracle.OracleContainer;
import org.testcontainers.oracle.OracleR2DBCDatabaseContainer;

import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

/**
* {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from
* a {@link ServiceConnection @ServiceConnection}-annotated {@link OracleContainer}.
*
* @author Eddú Meléndez
*/
class OracleFreeR2dbcContainerConnectionDetailsFactory
extends ContainerConnectionDetailsFactory<OracleContainer, R2dbcConnectionDetails> {

OracleFreeR2dbcContainerConnectionDetailsFactory() {
super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions");
}

@Override
public R2dbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource<OracleContainer> source) {
return new R2dbcDatabaseContainerConnectionDetails(source);
}

/**
* {@link R2dbcConnectionDetails} backed by a {@link ContainerConnectionSource}.
*/
private static final class R2dbcDatabaseContainerConnectionDetails
extends ContainerConnectionDetails<OracleContainer> implements R2dbcConnectionDetails {

private R2dbcDatabaseContainerConnectionDetails(ContainerConnectionSource<OracleContainer> source) {
super(source);
}

@Override
public ConnectionFactoryOptions getConnectionFactoryOptions() {
return OracleR2DBCDatabaseContainer.getOptions(getContainer());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
*
* @author Eddú Meléndez
*/
class OracleR2dbcContainerConnectionDetailsFactory
class OracleXeR2dbcContainerConnectionDetailsFactory
extends ContainerConnectionDetailsFactory<OracleContainer, R2dbcConnectionDetails> {

OracleR2dbcContainerConnectionDetailsFactory() {
OracleXeR2dbcContainerConnectionDetailsFactory() {
super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ org.springframework.boot.testcontainers.service.connection.otlp.OpenTelemetryTra
org.springframework.boot.testcontainers.service.connection.pulsar.PulsarContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.MariaDbR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.MySqlR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.OracleR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.OracleFreeR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.OracleXeR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.PostgresR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.r2dbc.SqlServerR2dbcContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory,\
Expand Down
Loading

0 comments on commit 6c3dec4

Please sign in to comment.