Skip to content

Commit b3992bc

Browse files
committed
Incremental work in testing DatabaseUpdater for real and fixing the Database mess.
* Allow in-memory databases in H2Connector (set path to 'mem:<db-name>'). * Remove some redundant copy-pasted readFileAsString() methods (more pending). * Remove unused executeSQL() method from Database and DatabaseConnector. * Get started on better testing in DatabaseUpdaterTest. * Improve DatabaseUpdater code. * Improve documentation and @nullable / @nonnull annotations. * Code quality.
1 parent 0a46edd commit b3992bc

10 files changed

Lines changed: 246 additions & 170 deletions

File tree

src/main/java/cloudgene/mapred/database/util/AbstractDatabaseConnector.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,18 @@
66

77
public abstract class AbstractDatabaseConnector implements DatabaseConnector {
88

9-
protected static final Logger log = LoggerFactory.getLogger(DatabaseUpdater.class);
9+
protected static final Logger log = LoggerFactory.getLogger(AbstractDatabaseConnector.class);
1010

1111
private int maxActive = 10;
12-
13-
private int maxWait = 10000;
14-
12+
private int maxWait = 10_000;
1513
private boolean defaultAutoCommit = true;
16-
1714
private boolean testWhileIdle = true;
18-
19-
private int minEvictableIdleTimeMillis = 1800000;
20-
21-
private int timeBetweenEvictionRunsMillis = 1800000;
15+
private int minEvictableIdleTimeMillis = 1_800_000;
16+
private int timeBetweenEvictionRunsMillis = 1_800_000;
2217

2318
protected BasicDataSource createDataSource() {
24-
2519
BasicDataSource dataSource = new BasicDataSource();
20+
2621
dataSource.setMaxActive(maxActive);
2722
dataSource.setMaxWait(maxWait);
2823
dataSource.setMaxIdle(maxActive);

src/main/java/cloudgene/mapred/database/util/Database.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
package cloudgene.mapred.database.util;
22

3-
import java.io.IOException;
4-
import java.io.InputStream;
5-
import java.net.URISyntaxException;
63
import java.sql.SQLException;
74
import java.util.ArrayList;
85
import java.util.List;
96

7+
import io.micronaut.core.annotation.NonNull;
8+
import io.micronaut.core.annotation.Nullable;
109
import org.apache.commons.dbcp.BasicDataSource;
1110
import org.slf4j.Logger;
1211
import org.slf4j.LoggerFactory;
1312

13+
// TODO(Marc): This class is a wrapper around DatabaseConnector and the only thing it
14+
// adds is listener support, but we don't use these listeners anywhere.
15+
// Remove.
1416
public class Database {
1517

1618
private static final Logger log = LoggerFactory.getLogger(Database.class);
1719

18-
private DatabaseConnector connector;
19-
20+
private @Nullable DatabaseConnector connector;
2021
private final List<DatabaseListener> listeners = new ArrayList<>();
2122

2223
public Database() {
2324
}
2425

25-
public void connect(DatabaseConnector connector) throws SQLException {
26+
public void connect(@NonNull DatabaseConnector connector) throws SQLException {
2627
this.connector = connector;
2728
try {
2829
connector.connect();
@@ -62,6 +63,7 @@ public boolean isConnected() {
6263
}
6364
}
6465

66+
// TODO(Marc): Connector is nullable, so this can throw a NullPointerException.
6567
public BasicDataSource getDataSource() {
6668
return connector.getDataSource();
6769
}
@@ -82,10 +84,7 @@ private void fireChangeEvent(int event) {
8284
}
8385
}
8486

85-
public void executeSQL(InputStream is) throws SQLException, IOException, URISyntaxException {
86-
connector.executeSQL(is);
87-
}
88-
87+
@Nullable
8988
public DatabaseConnector getConnector() {
9089
return connector;
9190
}
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
package cloudgene.mapred.database.util;
22

3-
import java.io.IOException;
4-
import java.io.InputStream;
5-
import java.net.URISyntaxException;
63
import java.sql.SQLException;
74

85
import org.apache.commons.dbcp.BasicDataSource;
96

107
public interface DatabaseConnector {
118

12-
public void connect() throws SQLException;
9+
void connect() throws SQLException;
1310

14-
public void disconnect() throws SQLException;
11+
void disconnect() throws SQLException;
1512

16-
public BasicDataSource getDataSource();
13+
BasicDataSource getDataSource();
1714

18-
public void executeSQL(InputStream is)
19-
throws SQLException, IOException, URISyntaxException;
20-
21-
public String getSchema();
15+
String getSchema();
2216

2317
boolean tableExists(String table) throws SQLException;
2418
}

src/main/java/cloudgene/mapred/database/util/DatabaseConnectorFactory.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,25 @@
44

55
import cloudgene.mapred.database.util.h2.H2Connector;
66
import cloudgene.mapred.database.util.mysql.MySqlConnector;
7+
import io.micronaut.core.annotation.NonNull;
8+
import io.micronaut.core.annotation.Nullable;
79

8-
public class DatabaseConnectorFactory {
10+
/**
11+
* Provides {@link #createConnector(Map)} to configure and return a
12+
* {@link DatabaseConnector}.
13+
*/
14+
public final class DatabaseConnectorFactory {
915

10-
public static DatabaseConnector createConnector(Map<String, String> settings) {
16+
private DatabaseConnectorFactory() {
17+
}
18+
19+
/**
20+
* Reads {@code settings} to configure and return either an {@link H2Connector}
21+
* or a {@link MySqlConnector}. The {@code} driver field is used to determine
22+
* the output type (should be {@code h2} or {@code mysql}).
23+
*/
24+
@Nullable
25+
public static DatabaseConnector createConnector(@NonNull Map<String, String> settings) {
1126
String driver = settings.get("driver");
1227

1328
if (driver == null) {

src/main/java/cloudgene/mapred/database/util/DatabaseUpdater.java

Lines changed: 78 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,53 @@
11
package cloudgene.mapred.database.util;
22

3-
import genepi.io.FileUtil;
4-
53
import java.io.BufferedReader;
6-
import java.io.DataInputStream;
74
import java.io.File;
85
import java.io.FileInputStream;
96
import java.io.IOException;
107
import java.io.InputStream;
118
import java.io.InputStreamReader;
129
import java.net.URISyntaxException;
10+
import java.net.URL;
1311
import java.sql.Connection;
1412
import java.sql.PreparedStatement;
1513
import java.sql.ResultSet;
1614
import java.sql.SQLException;
1715
import java.util.HashMap;
1816
import java.util.Map;
1917

18+
import io.micronaut.core.annotation.NonNull;
2019
import org.slf4j.Logger;
2120
import org.slf4j.LoggerFactory;
2221

2322
public class DatabaseUpdater {
2423

2524
protected static final Logger log = LoggerFactory.getLogger(DatabaseUpdater.class);
2625

27-
private final DatabaseConnector connector;
28-
private final Database database;
29-
private final String oldVersion;
30-
private final String currentVersion;
31-
private final String filename;
32-
private final InputStream updateFileAsStream;
26+
private final @NonNull Database database;
27+
private final @NonNull File versionFile;
28+
private final @NonNull URL updatesFile;
29+
private final @NonNull String currentVersion;
30+
31+
private final @NonNull DatabaseConnector connector;
32+
private final @NonNull Map<String, IUpdateListener> listeners;
33+
34+
private final @NonNull String oldVersion;
3335
private final boolean needUpdate;
34-
private final Map<String, IUpdateListener> listeners;
3536

36-
public DatabaseUpdater(Database database, String filename, InputStream updateFileAsStream, String currentVersion) {
37-
this.filename = filename;
37+
public DatabaseUpdater(
38+
@NonNull Database database,
39+
@NonNull File versionFile,
40+
@NonNull URL updatesFile,
41+
@NonNull String currentVersion) {
42+
3843
this.database = database;
39-
this.connector = database.getConnector();
40-
this.updateFileAsStream = updateFileAsStream;
44+
this.versionFile = versionFile;
45+
this.updatesFile = updatesFile;
4146
this.currentVersion = currentVersion;
47+
48+
// TODO(Marc): database.getConnector() is nullable, but here we assume connector
49+
// is not null!
50+
this.connector = database.getConnector();
4251
this.listeners = new HashMap<>();
4352

4453
if (isVersionTableAvailable()) {
@@ -47,30 +56,38 @@ public DatabaseUpdater(Database database, String filename, InputStream updateFil
4756

4857
// Should not happen, since an entry is created when metadata table exists.
4958
if (oldVersion == null) {
50-
oldVersion = readVersion(filename);
59+
oldVersion = readVersion();
5160
log.info("Read current version from DB was not successful, read it from file: {}", oldVersion);
5261
}
5362

5463
this.oldVersion = oldVersion;
5564
} else {
5665
// check also file for backwards compatibility
57-
this.oldVersion = readVersion(filename);
66+
this.oldVersion = readVersion();
5867
log.info("Read current version from file: {}", oldVersion);
5968
}
6069

6170
log.info("Current app version: {}", currentVersion);
6271
needUpdate = (compareVersion(currentVersion, oldVersion) > 0);
6372
}
6473

65-
public void addUpdate(String version, IUpdateListener listener) {
74+
/**
75+
* Assigns {@code listener} as the one and only update listener for
76+
* {@code version}.
77+
* Replaces any existing listeners for the same version.
78+
*/
79+
public void addListener(String version, IUpdateListener listener) {
6680
listeners.put(version, listener);
6781
}
6882

83+
/**
84+
* If the database needs updating, updates it. Otherwise, inserts the current
85+
* version to the version table.
86+
*/
6987
public boolean updateDB() {
70-
7188
if (needUpdate()) {
7289
log.info("Database needs update...");
73-
if (!update()) {
90+
if (!update()) { // TODO: update() ALWAYS returns true...
7491
log.error("Updating database failed.");
7592
try {
7693
database.disconnect();
@@ -98,12 +115,13 @@ public boolean updateDB() {
98115
return true;
99116
}
100117

101-
public boolean update() {
118+
// TODO(Marc): Only called from updateDB(). Consider merging.
119+
private boolean update() {
102120
if (needUpdate) {
103121
log.info("Updating database from {} to {}...", oldVersion, currentVersion);
104122

105123
try {
106-
readAndPrepareSqlClasspath(updateFileAsStream, oldVersion, currentVersion);
124+
readAndPrepareSqlClasspath(oldVersion, currentVersion);
107125
} catch (IOException | URISyntaxException | SQLException e) {
108126
// TODO Auto-generated catch block
109127
e.printStackTrace();
@@ -129,20 +147,27 @@ public boolean needUpdate() {
129147
return needUpdate;
130148
}
131149

132-
public void writeVersion(String newVersion) {
150+
/**
151+
* Creates the database version table if not already present, and inserts
152+
* {@code version} as the latest version. If a version file exists, it is
153+
* deleted. Exceptions are ignored.
154+
*
155+
* @param version Newest application version (semver format expected).
156+
*/
157+
private void writeVersion(String version) {
133158
try {
134159
if (!isVersionTableAvailable()) {
135160
createVersionTable();
136161
}
137162

138163
Connection connection = connector.getDataSource().getConnection();
139164
PreparedStatement ps = connection.prepareStatement("INSERT INTO database_versions (version) VALUES (?)");
140-
ps.setString(1, newVersion);
165+
ps.setString(1, version);
141166
ps.executeUpdate();
142-
log.info("Version in DB updated to: {}", newVersion);
167+
log.info("Version in DB updated to: {}", version);
143168

144-
if (new File(filename).exists()) {
145-
FileUtil.deleteFile(filename);
169+
if (versionFile.exists()) {
170+
versionFile.delete();
146171
log.info("Deleted version.txt on file system.");
147172
}
148173

@@ -153,10 +178,13 @@ public void writeVersion(String newVersion) {
153178
}
154179
}
155180

156-
public String readVersion(String versionFile) {
157-
File file = new File(versionFile);
158-
159-
if (file.exists()) {
181+
/**
182+
* If {@code versionFile} exists, returns its contents (expects semver version).
183+
* Defaults to {@code 0.0.0}.
184+
*/
185+
@NonNull
186+
private String readVersion() {
187+
if (versionFile.exists()) {
160188
try {
161189
return readFileAsString(versionFile);
162190
} catch (Exception e) {
@@ -167,7 +195,7 @@ public String readVersion(String versionFile) {
167195
}
168196
}
169197

170-
public String readVersionDB() {
198+
private String readVersionDB() {
171199
String sql = "SELECT version FROM database_versions "
172200
+ "WHERE updated_on = (SELECT MAX(updated_on) FROM database_versions) "
173201
+ "ORDER BY updated_on, id DESC";
@@ -193,27 +221,33 @@ public String readVersionDB() {
193221
return version;
194222
}
195223

196-
public static String readFileAsString(String filename) throws java.io.IOException, URISyntaxException {
197-
InputStream is = new FileInputStream(filename);
224+
// TODO(Marc): Why is this here???
225+
private static String readFileAsString(File file) throws IOException {
226+
InputStream is = new FileInputStream(file);
227+
InputStreamReader sr = new InputStreamReader(is);
228+
BufferedReader br = new BufferedReader(sr);
198229

199-
DataInputStream in = new DataInputStream(is);
200-
BufferedReader br = new BufferedReader(new InputStreamReader(in));
201230
String strLine;
202231
StringBuilder builder = new StringBuilder();
203232

204233
while ((strLine = br.readLine()) != null) {
205234
builder.append(strLine);
206235
}
207236

208-
in.close();
237+
br.close();
238+
sr.close();
239+
is.close();
240+
209241
return builder.toString();
210242
}
211243

212-
public String readAndPrepareSqlClasspath(InputStream filestream, String minVersion, String maxVersion)
213-
throws java.io.IOException, URISyntaxException, SQLException {
244+
private String readAndPrepareSqlClasspath(String minVersion, String maxVersion)
245+
throws IOException, URISyntaxException, SQLException {
246+
247+
InputStream is = updatesFile.openStream();
248+
InputStreamReader sr = new InputStreamReader(is);
249+
BufferedReader br = new BufferedReader(sr);
214250

215-
DataInputStream in = new DataInputStream(filestream);
216-
BufferedReader br = new BufferedReader(new InputStreamReader(in));
217251
String strLine;
218252
StringBuilder builder = new StringBuilder();
219253
boolean reading = false;
@@ -250,7 +284,10 @@ public String readAndPrepareSqlClasspath(InputStream filestream, String minVersi
250284
// last block
251285
executeSQLFile(builder.toString(), version);
252286

253-
in.close();
287+
br.close();
288+
sr.close();
289+
is.close();
290+
254291
return builder.toString();
255292
}
256293

0 commit comments

Comments
 (0)