Skip to content

Commit

Permalink
Add Oracle Free module (#7749)
Browse files Browse the repository at this point in the history
Fixes #7190

---------

Signed-off-by: gvenzl <gerald.venzl@gmail.com>
Co-authored-by: Eddú Meléndez Gonzales <eddu.melendez@gmail.com>
  • Loading branch information
gvenzl and eddumelendez committed Nov 3, 2023
1 parent b596ae4 commit fa23ae4
Show file tree
Hide file tree
Showing 16 changed files with 617 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ body:
- MySQL
- Neo4j
- NGINX
- Oracle-XE
- Oracle Free
- Oracle XE
- OrientDB
- PostgreSQL
- Presto
Expand Down
3 changes: 2 additions & 1 deletion .github/ISSUE_TEMPLATE/enhancement.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ body:
- MySQL
- Neo4j
- NGINX
- Oracle-XE
- Oracle Free
- Oracle XE
- OrientDB
- PostgreSQL
- Presto
Expand Down
3 changes: 2 additions & 1 deletion .github/ISSUE_TEMPLATE/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ body:
- MySQL
- Neo4j
- NGINX
- Oracle-XE
- Oracle Free
- Oracle XE
- OrientDB
- PostgreSQL
- QuestDB
Expand Down
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "gradle"
directory: "/modules/oracle-free"
schedule:
interval: "weekly"
- package-ecosystem: "gradle"
directory: "/modules/oracle-xe"
schedule:
Expand Down
1 change: 1 addition & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"modules/nginx":
- modules/nginx/**/*
"modules/oracle":
- modules/oracle-free/**/*
- modules/oracle-xe/**/*
"modules/orientdb":
- modules/orientdb/**/*
Expand Down
33 changes: 33 additions & 0 deletions docs/modules/databases/oraclefree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Oracle Database Free Module

See [Database containers](./index.md) for documentation and usage that is common to all relational database container types.

## Usage example

You can use `OracleContainer` like any other JDBC container:
<!--codeinclude-->
[Container creation](../../../modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java) inside_block:constructor
<!--/codeinclude-->

## Adding this module to your project dependencies

Add the following dependency to your `pom.xml`/`build.gradle` file:

=== "Gradle"
```groovy
testImplementation "org.testcontainers:oracle-free:{{latest_version}}"
```
=== "Maven"
```xml
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-free</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```

!!! hint
Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.


32 changes: 32 additions & 0 deletions modules/oracle-free/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
description = "Testcontainers :: JDBC :: Oracle Database Free"

dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
compileOnly 'com.google.auto.service:auto-service:1.1.1'

api project(':jdbc')

compileOnly project(':r2dbc')
compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.1.1'

testImplementation project(':jdbc-test')
testImplementation 'com.oracle.database.jdbc:ojdbc11:23.3.0.23.09'

compileOnly 'org.jetbrains:annotations:24.0.1'

testImplementation testFixtures(project(':r2dbc'))
testRuntimeOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.1.1'
}

test {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}

compileTestJava {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
options.release.set(11)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package org.testcontainers.oracle;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
* Testcontainers implementation for Oracle Database Free.
* <p>
* Supported image: {@code gvenzl/oracle-free}
* <p>
* Exposed ports: 1521
*/
public class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {

static final String NAME = "oracle";

private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gvenzl/oracle-free");

static final String DEFAULT_TAG = "slim";

static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();

static final int ORACLE_PORT = 1521;

private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 60;

private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 60;

// Container defaults
static final String DEFAULT_DATABASE_NAME = "freepdb1";

static final String DEFAULT_SID = "free";

static final String DEFAULT_SYSTEM_USER = "system";

static final String DEFAULT_SYS_USER = "sys";

// Test container defaults
static final String APP_USER = "test";

static final String APP_USER_PASSWORD = "test";

// Restricted user and database names
private static final List<String> ORACLE_SYSTEM_USERS = Arrays.asList(DEFAULT_SYSTEM_USER, DEFAULT_SYS_USER);

private String databaseName = DEFAULT_DATABASE_NAME;

private String username = APP_USER;

private String password = APP_USER_PASSWORD;

private boolean usingSid = false;

public OracleContainer(String dockerImageName) {
this(DockerImageName.parse(dockerImageName));
}

public OracleContainer(final DockerImageName dockerImageName) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
this.waitStrategy =
new LogMessageWaitStrategy()
.withRegEx(".*DATABASE IS READY TO USE!.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(DEFAULT_STARTUP_TIMEOUT_SECONDS, ChronoUnit.SECONDS));
withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);
addExposedPorts(ORACLE_PORT);
}

@Override
protected void waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this);
}

@NotNull
@Override
public Set<Integer> getLivenessCheckPortNumbers() {
return Collections.singleton(getMappedPort(ORACLE_PORT));
}

@Override
public String getDriverClassName() {
return "oracle.jdbc.driver.OracleDriver";
}

@Override
public String getJdbcUrl() {
return isUsingSid()
? "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + ":" + getSid()
: "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + "/" + getDatabaseName();
}

@Override
public String getUsername() {
// An application user is tied to the database, and therefore not authenticated to connect to SID.
return isUsingSid() ? DEFAULT_SYSTEM_USER : username;
}

@Override
public String getPassword() {
return password;
}

@Override
public String getDatabaseName() {
return databaseName;
}

protected boolean isUsingSid() {
return usingSid;
}

@Override
public OracleContainer withUsername(String username) {
if (StringUtils.isEmpty(username)) {
throw new IllegalArgumentException("Username cannot be null or empty");
}
if (ORACLE_SYSTEM_USERS.contains(username.toLowerCase())) {
throw new IllegalArgumentException("Username cannot be one of " + ORACLE_SYSTEM_USERS);
}
this.username = username;
return self();
}

@Override
public OracleContainer withPassword(String password) {
if (StringUtils.isEmpty(password)) {
throw new IllegalArgumentException("Password cannot be null or empty");
}
this.password = password;
return self();
}

@Override
public OracleContainer withDatabaseName(String databaseName) {
if (StringUtils.isEmpty(databaseName)) {
throw new IllegalArgumentException("Database name cannot be null or empty");
}

if (DEFAULT_DATABASE_NAME.equals(databaseName.toLowerCase())) {
throw new IllegalArgumentException("Database name cannot be set to " + DEFAULT_DATABASE_NAME);
}

this.databaseName = databaseName;
return self();
}

public OracleContainer usingSid() {
this.usingSid = true;
return self();
}

@Override
public OracleContainer withUrlParam(String paramName, String paramValue) {
throw new UnsupportedOperationException("The Oracle Database driver does not support this");
}

@SuppressWarnings("SameReturnValue")
public String getSid() {
return DEFAULT_SID;
}

public Integer getOraclePort() {
return getMappedPort(ORACLE_PORT);
}

@Override
public String getTestQueryString() {
return "SELECT 1 FROM DUAL";
}

@Override
protected void configure() {
withEnv("ORACLE_PASSWORD", password);

// Only set ORACLE_DATABASE if different than the default.
if (databaseName != DEFAULT_DATABASE_NAME) {
withEnv("ORACLE_DATABASE", databaseName);
}

withEnv("APP_USER", username);
withEnv("APP_USER_PASSWORD", password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.testcontainers.oracle;

import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.JdbcDatabaseContainerProvider;
import org.testcontainers.utility.DockerImageName;

/**
* Factory for Oracle containers.
*/
public class OracleContainerProvider extends JdbcDatabaseContainerProvider {

@Override
public boolean supports(String databaseType) {
return databaseType.equals(OracleContainer.NAME);
}

@Override
public JdbcDatabaseContainer newInstance() {
return newInstance(OracleContainer.DEFAULT_TAG);
}

@Override
public JdbcDatabaseContainer newInstance(String tag) {
if (tag != null) {
return new OracleContainer(DockerImageName.parse(OracleContainer.IMAGE).withTag(tag));
}
return newInstance();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.testcontainers.oracle;

import io.r2dbc.spi.ConnectionFactoryOptions;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import org.testcontainers.lifecycle.Startable;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;

@RequiredArgsConstructor
public class OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {

@Delegate(types = Startable.class)
private final OracleContainer container;

public static ConnectionFactoryOptions getOptions(OracleContainer container) {
ConnectionFactoryOptions options = ConnectionFactoryOptions
.builder()
.option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)
.build();

return new OracleR2DBCDatabaseContainer(container).configure(options);
}

@Override
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
return options
.mutate()
.option(ConnectionFactoryOptions.HOST, container.getHost())
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_PORT))
.option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())
.option(ConnectionFactoryOptions.USER, container.getUsername())
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
.build();
}
}
Loading

0 comments on commit fa23ae4

Please sign in to comment.