Skip to content

Commit fa23ae4

Browse files
gvenzleddumelendez
andauthored
Add Oracle Free module (#7749)
Fixes #7190 --------- Signed-off-by: gvenzl <[email protected]> Co-authored-by: Eddú Meléndez Gonzales <[email protected]>
1 parent b596ae4 commit fa23ae4

File tree

16 files changed

+617
-3
lines changed

16 files changed

+617
-3
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ body:
3838
- MySQL
3939
- Neo4j
4040
- NGINX
41-
- Oracle-XE
41+
- Oracle Free
42+
- Oracle XE
4243
- OrientDB
4344
- PostgreSQL
4445
- Presto

.github/ISSUE_TEMPLATE/enhancement.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ body:
3838
- MySQL
3939
- Neo4j
4040
- NGINX
41-
- Oracle-XE
41+
- Oracle Free
42+
- Oracle XE
4243
- OrientDB
4344
- PostgreSQL
4445
- Presto

.github/ISSUE_TEMPLATE/feature.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ body:
3838
- MySQL
3939
- Neo4j
4040
- NGINX
41-
- Oracle-XE
41+
- Oracle Free
42+
- Oracle XE
4243
- OrientDB
4344
- PostgreSQL
4445
- QuestDB

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ updates:
194194
schedule:
195195
interval: "weekly"
196196
open-pull-requests-limit: 10
197+
- package-ecosystem: "gradle"
198+
directory: "/modules/oracle-free"
199+
schedule:
200+
interval: "weekly"
197201
- package-ecosystem: "gradle"
198202
directory: "/modules/oracle-xe"
199203
schedule:

.github/labeler.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"modules/nginx":
6363
- modules/nginx/**/*
6464
"modules/oracle":
65+
- modules/oracle-free/**/*
6566
- modules/oracle-xe/**/*
6667
"modules/orientdb":
6768
- modules/orientdb/**/*
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Oracle Database Free Module
2+
3+
See [Database containers](./index.md) for documentation and usage that is common to all relational database container types.
4+
5+
## Usage example
6+
7+
You can use `OracleContainer` like any other JDBC container:
8+
<!--codeinclude-->
9+
[Container creation](../../../modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java) inside_block:constructor
10+
<!--/codeinclude-->
11+
12+
## Adding this module to your project dependencies
13+
14+
Add the following dependency to your `pom.xml`/`build.gradle` file:
15+
16+
=== "Gradle"
17+
```groovy
18+
testImplementation "org.testcontainers:oracle-free:{{latest_version}}"
19+
```
20+
=== "Maven"
21+
```xml
22+
<dependency>
23+
<groupId>org.testcontainers</groupId>
24+
<artifactId>oracle-free</artifactId>
25+
<version>{{latest_version}}</version>
26+
<scope>test</scope>
27+
</dependency>
28+
```
29+
30+
!!! hint
31+
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+
33+

modules/oracle-free/build.gradle

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
description = "Testcontainers :: JDBC :: Oracle Database Free"
2+
3+
dependencies {
4+
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
5+
compileOnly 'com.google.auto.service:auto-service:1.1.1'
6+
7+
api project(':jdbc')
8+
9+
compileOnly project(':r2dbc')
10+
compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.1.1'
11+
12+
testImplementation project(':jdbc-test')
13+
testImplementation 'com.oracle.database.jdbc:ojdbc11:23.3.0.23.09'
14+
15+
compileOnly 'org.jetbrains:annotations:24.0.1'
16+
17+
testImplementation testFixtures(project(':r2dbc'))
18+
testRuntimeOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.1.1'
19+
}
20+
21+
test {
22+
javaLauncher = javaToolchains.launcherFor {
23+
languageVersion = JavaLanguageVersion.of(11)
24+
}
25+
}
26+
27+
compileTestJava {
28+
javaCompiler = javaToolchains.compilerFor {
29+
languageVersion = JavaLanguageVersion.of(11)
30+
}
31+
options.release.set(11)
32+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package org.testcontainers.oracle;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.testcontainers.containers.JdbcDatabaseContainer;
6+
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
7+
import org.testcontainers.utility.DockerImageName;
8+
9+
import java.time.Duration;
10+
import java.time.temporal.ChronoUnit;
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.List;
14+
import java.util.Set;
15+
16+
/**
17+
* Testcontainers implementation for Oracle Database Free.
18+
* <p>
19+
* Supported image: {@code gvenzl/oracle-free}
20+
* <p>
21+
* Exposed ports: 1521
22+
*/
23+
public class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {
24+
25+
static final String NAME = "oracle";
26+
27+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gvenzl/oracle-free");
28+
29+
static final String DEFAULT_TAG = "slim";
30+
31+
static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();
32+
33+
static final int ORACLE_PORT = 1521;
34+
35+
private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 60;
36+
37+
private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 60;
38+
39+
// Container defaults
40+
static final String DEFAULT_DATABASE_NAME = "freepdb1";
41+
42+
static final String DEFAULT_SID = "free";
43+
44+
static final String DEFAULT_SYSTEM_USER = "system";
45+
46+
static final String DEFAULT_SYS_USER = "sys";
47+
48+
// Test container defaults
49+
static final String APP_USER = "test";
50+
51+
static final String APP_USER_PASSWORD = "test";
52+
53+
// Restricted user and database names
54+
private static final List<String> ORACLE_SYSTEM_USERS = Arrays.asList(DEFAULT_SYSTEM_USER, DEFAULT_SYS_USER);
55+
56+
private String databaseName = DEFAULT_DATABASE_NAME;
57+
58+
private String username = APP_USER;
59+
60+
private String password = APP_USER_PASSWORD;
61+
62+
private boolean usingSid = false;
63+
64+
public OracleContainer(String dockerImageName) {
65+
this(DockerImageName.parse(dockerImageName));
66+
}
67+
68+
public OracleContainer(final DockerImageName dockerImageName) {
69+
super(dockerImageName);
70+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
71+
this.waitStrategy =
72+
new LogMessageWaitStrategy()
73+
.withRegEx(".*DATABASE IS READY TO USE!.*\\s")
74+
.withTimes(1)
75+
.withStartupTimeout(Duration.of(DEFAULT_STARTUP_TIMEOUT_SECONDS, ChronoUnit.SECONDS));
76+
withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);
77+
addExposedPorts(ORACLE_PORT);
78+
}
79+
80+
@Override
81+
protected void waitUntilContainerStarted() {
82+
getWaitStrategy().waitUntilReady(this);
83+
}
84+
85+
@NotNull
86+
@Override
87+
public Set<Integer> getLivenessCheckPortNumbers() {
88+
return Collections.singleton(getMappedPort(ORACLE_PORT));
89+
}
90+
91+
@Override
92+
public String getDriverClassName() {
93+
return "oracle.jdbc.driver.OracleDriver";
94+
}
95+
96+
@Override
97+
public String getJdbcUrl() {
98+
return isUsingSid()
99+
? "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + ":" + getSid()
100+
: "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + "/" + getDatabaseName();
101+
}
102+
103+
@Override
104+
public String getUsername() {
105+
// An application user is tied to the database, and therefore not authenticated to connect to SID.
106+
return isUsingSid() ? DEFAULT_SYSTEM_USER : username;
107+
}
108+
109+
@Override
110+
public String getPassword() {
111+
return password;
112+
}
113+
114+
@Override
115+
public String getDatabaseName() {
116+
return databaseName;
117+
}
118+
119+
protected boolean isUsingSid() {
120+
return usingSid;
121+
}
122+
123+
@Override
124+
public OracleContainer withUsername(String username) {
125+
if (StringUtils.isEmpty(username)) {
126+
throw new IllegalArgumentException("Username cannot be null or empty");
127+
}
128+
if (ORACLE_SYSTEM_USERS.contains(username.toLowerCase())) {
129+
throw new IllegalArgumentException("Username cannot be one of " + ORACLE_SYSTEM_USERS);
130+
}
131+
this.username = username;
132+
return self();
133+
}
134+
135+
@Override
136+
public OracleContainer withPassword(String password) {
137+
if (StringUtils.isEmpty(password)) {
138+
throw new IllegalArgumentException("Password cannot be null or empty");
139+
}
140+
this.password = password;
141+
return self();
142+
}
143+
144+
@Override
145+
public OracleContainer withDatabaseName(String databaseName) {
146+
if (StringUtils.isEmpty(databaseName)) {
147+
throw new IllegalArgumentException("Database name cannot be null or empty");
148+
}
149+
150+
if (DEFAULT_DATABASE_NAME.equals(databaseName.toLowerCase())) {
151+
throw new IllegalArgumentException("Database name cannot be set to " + DEFAULT_DATABASE_NAME);
152+
}
153+
154+
this.databaseName = databaseName;
155+
return self();
156+
}
157+
158+
public OracleContainer usingSid() {
159+
this.usingSid = true;
160+
return self();
161+
}
162+
163+
@Override
164+
public OracleContainer withUrlParam(String paramName, String paramValue) {
165+
throw new UnsupportedOperationException("The Oracle Database driver does not support this");
166+
}
167+
168+
@SuppressWarnings("SameReturnValue")
169+
public String getSid() {
170+
return DEFAULT_SID;
171+
}
172+
173+
public Integer getOraclePort() {
174+
return getMappedPort(ORACLE_PORT);
175+
}
176+
177+
@Override
178+
public String getTestQueryString() {
179+
return "SELECT 1 FROM DUAL";
180+
}
181+
182+
@Override
183+
protected void configure() {
184+
withEnv("ORACLE_PASSWORD", password);
185+
186+
// Only set ORACLE_DATABASE if different than the default.
187+
if (databaseName != DEFAULT_DATABASE_NAME) {
188+
withEnv("ORACLE_DATABASE", databaseName);
189+
}
190+
191+
withEnv("APP_USER", username);
192+
withEnv("APP_USER_PASSWORD", password);
193+
}
194+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.testcontainers.oracle;
2+
3+
import org.testcontainers.containers.JdbcDatabaseContainer;
4+
import org.testcontainers.containers.JdbcDatabaseContainerProvider;
5+
import org.testcontainers.utility.DockerImageName;
6+
7+
/**
8+
* Factory for Oracle containers.
9+
*/
10+
public class OracleContainerProvider extends JdbcDatabaseContainerProvider {
11+
12+
@Override
13+
public boolean supports(String databaseType) {
14+
return databaseType.equals(OracleContainer.NAME);
15+
}
16+
17+
@Override
18+
public JdbcDatabaseContainer newInstance() {
19+
return newInstance(OracleContainer.DEFAULT_TAG);
20+
}
21+
22+
@Override
23+
public JdbcDatabaseContainer newInstance(String tag) {
24+
if (tag != null) {
25+
return new OracleContainer(DockerImageName.parse(OracleContainer.IMAGE).withTag(tag));
26+
}
27+
return newInstance();
28+
}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.testcontainers.oracle;
2+
3+
import io.r2dbc.spi.ConnectionFactoryOptions;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.experimental.Delegate;
6+
import org.testcontainers.lifecycle.Startable;
7+
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
8+
9+
@RequiredArgsConstructor
10+
public class OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {
11+
12+
@Delegate(types = Startable.class)
13+
private final OracleContainer container;
14+
15+
public static ConnectionFactoryOptions getOptions(OracleContainer container) {
16+
ConnectionFactoryOptions options = ConnectionFactoryOptions
17+
.builder()
18+
.option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)
19+
.build();
20+
21+
return new OracleR2DBCDatabaseContainer(container).configure(options);
22+
}
23+
24+
@Override
25+
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
26+
return options
27+
.mutate()
28+
.option(ConnectionFactoryOptions.HOST, container.getHost())
29+
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_PORT))
30+
.option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())
31+
.option(ConnectionFactoryOptions.USER, container.getUsername())
32+
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
33+
.build();
34+
}
35+
}

0 commit comments

Comments
 (0)