Skip to content

Commit

Permalink
added the ability to change the name of the db tables; improved concu…
Browse files Browse the repository at this point in the history
…rrency tests
  • Loading branch information
pwinckles committed Nov 19, 2020
1 parent e7d006f commit 8f65d51
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 166 deletions.
4 changes: 4 additions & 0 deletions ocfl-java-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,48 @@ public abstract class BaseObjectDetailsDatabase implements ObjectDetailsDatabase

private static final Logger LOG = LoggerFactory.getLogger(BaseObjectDetailsDatabase.class);

private final String tableName;
private final DataSource dataSource;
private final boolean storeInventory;
private final long waitMillis;

private final String lockFailCode;
private final String duplicateKeyCode;

public BaseObjectDetailsDatabase(DataSource dataSource, boolean storeInventory, long waitTime, TimeUnit timeUnit,
String lockFailCode, String duplicateKeyCode) {
private final String selectDetailsQuery;
private final String deleteDetailsQuery;
private final String rowLockQuery;
private final String updateDetailsQuery;
private final String insertDetailsQuery;
private final String selectDigestQuery;

public BaseObjectDetailsDatabase(String tableName,
DataSource dataSource,
boolean storeInventory,
long waitTime,
TimeUnit timeUnit,
String lockFailCode,
String duplicateKeyCode) {
this.tableName = Enforce.notBlank(tableName, "tableName cannot be blank");
this.dataSource = Enforce.notNull(dataSource, "dataSource cannot be null");
this.storeInventory = storeInventory;
this.lockFailCode = Enforce.notBlank(lockFailCode, "lockFailCode cannot be blank");
this.duplicateKeyCode = Enforce.notBlank(duplicateKeyCode, "duplicateKeyCode cannot be blank");
this.waitMillis = timeUnit.toMillis(waitTime);

this.selectDetailsQuery = String.format("SELECT" +
" object_id, version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp" +
" FROM %s WHERE object_id = ?", tableName);
this.deleteDetailsQuery = String.format("DELETE FROM %s WHERE object_id = ?", tableName);
this.rowLockQuery = String.format("SELECT version_id, revision_id FROM %s WHERE object_id = ? FOR UPDATE", tableName);
this.updateDetailsQuery = String.format("UPDATE %s SET" +
" (version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp)" +
" = (?, ?, ?, ?, ?, ?, ?)" +
" WHERE object_id = ?", tableName);
this.insertDetailsQuery = String.format("INSERT INTO %s" +
" (object_id, version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp)" +
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)", tableName);
this.selectDigestQuery = String.format("SELECT inventory_digest FROM %s WHERE object_id = ?", tableName);
}

/**
Expand All @@ -89,9 +117,7 @@ public OcflObjectDetails retrieveObjectDetails(String objectId) {
OcflObjectDetails details = null;

try (var connection = dataSource.getConnection()) {
try (var statement = connection.prepareStatement("SELECT" +
" object_id, version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp" +
" FROM ocfl_object_details WHERE object_id = ?")) {
try (var statement = connection.prepareStatement(selectDetailsQuery)) {
statement.setString(1, objectId);

try (var rs = statement.executeQuery()) {
Expand Down Expand Up @@ -164,7 +190,7 @@ public void deleteObjectDetails(String objectId) {
connection.setAutoCommit(false);
setLockWaitTimeout(connection, waitMillis);

try (var statement = connection.prepareStatement("DELETE FROM ocfl_object_details WHERE object_id = ?")) {
try (var statement = connection.prepareStatement(deleteDetailsQuery)) {
statement.setString(1, objectId);
statement.executeUpdate();
connection.commit();
Expand Down Expand Up @@ -201,7 +227,7 @@ private void updateObjectDetailsInternal(Inventory inventory, String inventoryDi
}

private void insertInventory(Connection connection, Inventory inventory, String inventoryDigest, InputStream inventoryStream) throws SQLException {
try (var lockStatement = connection.prepareStatement("SELECT version_id, revision_id FROM ocfl_object_details WHERE object_id = ? FOR UPDATE")) {
try (var lockStatement = connection.prepareStatement(rowLockQuery)) {
lockStatement.setString(1, inventory.getId());

try (var lockResult = lockStatement.executeQuery()) {
Expand All @@ -222,10 +248,7 @@ private void insertInventory(Connection connection, Inventory inventory, String
}

private void executeUpdateDetails(Connection connection, Inventory inventory, String inventoryDigest, InputStream inventoryStream) throws SQLException {
try (var insertStatement = connection.prepareStatement("UPDATE ocfl_object_details SET" +
" (version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp)" +
" = (?, ?, ?, ?, ?, ?, ?)" +
" WHERE object_id = ?")) {
try (var insertStatement = connection.prepareStatement(updateDetailsQuery)) {
insertStatement.setString(1, inventory.getHead().toString());
insertStatement.setString(2, inventory.getObjectRootPath());
insertStatement.setString(3, revisionNumStr(inventory.getRevisionNum()));
Expand All @@ -244,9 +267,7 @@ private void executeUpdateDetails(Connection connection, Inventory inventory, St
}

private void executeInsertDetails(Connection connection, Inventory inventory, String inventoryDigest, InputStream inventoryStream) throws SQLException {
try (var insertStatement = connection.prepareStatement("INSERT INTO ocfl_object_details" +
" (object_id, version_id, object_root_path, revision_id, inventory_digest, digest_algorithm, inventory, update_timestamp)" +
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) {
try (var insertStatement = connection.prepareStatement(insertDetailsQuery)) {
insertStatement.setString(1, inventory.getId());
insertStatement.setString(2, inventory.getHead().toString());
insertStatement.setString(3, inventory.getObjectRootPath());
Expand All @@ -271,7 +292,7 @@ private void executeInsertDetails(Connection connection, Inventory inventory, St

private String retrieveDigest(String objectId) {
try (var connection = dataSource.getConnection()) {
try (var statement = connection.prepareStatement("SELECT inventory_digest FROM ocfl_object_details WHERE object_id = ?")) {
try (var statement = connection.prepareStatement(selectDigestQuery)) {
statement.setString(1, objectId);

try (var resultSet = statement.executeQuery()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public class H2ObjectDetailsDatabase extends BaseObjectDetailsDatabase {
private static final String LOCK_FAIL_STATE = "HYT00";
private static final String DUPLICATE_KEY_STATE = "23505";

public H2ObjectDetailsDatabase(DataSource dataSource, boolean storeInventory, long waitTime, TimeUnit timeUnit) {
super(dataSource, storeInventory, waitTime, timeUnit, LOCK_FAIL_STATE, DUPLICATE_KEY_STATE);
public H2ObjectDetailsDatabase(String tableName, DataSource dataSource, boolean storeInventory, long waitTime, TimeUnit timeUnit) {
super(tableName, dataSource, storeInventory, waitTime, timeUnit, LOCK_FAIL_STATE, DUPLICATE_KEY_STATE);
}

protected void setLockWaitTimeout(Connection connection, long waitMillis) throws SQLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
*/
public class ObjectDetailsDatabaseBuilder {

private static final String DEFAULT_TABLE_NAME = "ocfl_object_details";

private boolean storeInventory;
private long waitTime;
private TimeUnit timeUnit;
private DataSource dataSource;
private String tableName;

public ObjectDetailsDatabaseBuilder() {
storeInventory = true;
Expand Down Expand Up @@ -81,6 +84,17 @@ public ObjectDetailsDatabaseBuilder dataSource(DataSource dataSource) {
return this;
}

/**
* Sets the name of the table to use to store object details. Default: ocfl_object_details
*
* @param tableName the table name to use
* @return builder
*/
public ObjectDetailsDatabaseBuilder tableName(String tableName) {
this.tableName = tableName;
return this;
}

/**
* Constructs a new {@link ObjectDetailsDatabase} instance using the given dataSource. If the database does not
* already contain an object details table, it attempts to create one.
Expand All @@ -90,21 +104,23 @@ public ObjectDetailsDatabaseBuilder dataSource(DataSource dataSource) {
public ObjectDetailsDatabase build() {
Enforce.notNull(dataSource, "dataSource cannot be null");

var resolvedTableName = tableName == null ? DEFAULT_TABLE_NAME : tableName;

var dbType = DbType.fromDataSource(dataSource);
ObjectDetailsDatabase database;

switch (dbType) {
case POSTGRES:
database = new PostgresObjectDetailsDatabase(dataSource, storeInventory, waitTime, timeUnit);
database = new PostgresObjectDetailsDatabase(resolvedTableName, dataSource, storeInventory, waitTime, timeUnit);
break;
case H2:
database = new H2ObjectDetailsDatabase(dataSource, storeInventory, waitTime, timeUnit);
database = new H2ObjectDetailsDatabase(resolvedTableName, dataSource, storeInventory, waitTime, timeUnit);
break;
default:
throw new OcflJavaException(String.format("Database type %s is not mapped to an ObjectDetailsDatabase implementation.", dbType));
}

new TableCreator(dbType, dataSource).createObjectDetailsTable();
new TableCreator(dbType, dataSource).createObjectDetailsTable(resolvedTableName);

return database;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public class PostgresObjectDetailsDatabase extends BaseObjectDetailsDatabase {
private static final String LOCK_FAIL_STATE = "55P03";
private static final String DUPLICATE_KEY_STATE = "23505";

public PostgresObjectDetailsDatabase(DataSource dataSource, boolean storeInventory, long waitTime, TimeUnit timeUnit) {
super(dataSource, storeInventory, waitTime, timeUnit, LOCK_FAIL_STATE, DUPLICATE_KEY_STATE);
public PostgresObjectDetailsDatabase(String tableName, DataSource dataSource, boolean storeInventory, long waitTime, TimeUnit timeUnit) {
super(tableName, dataSource, storeInventory, waitTime, timeUnit, LOCK_FAIL_STATE, DUPLICATE_KEY_STATE);
}

protected void setLockWaitTimeout(Connection connection, long waitMillis) throws SQLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,40 @@ public class TableCreator {

private static final Logger LOG = LoggerFactory.getLogger(TableCreator.class);

private static final String LOCK_TABLE_FILE = "ocfl_object_lock.sql";
private static final String OBJECT_DETAILS_TABLE_FILE = "ocfl_object_details.sql";
private static final String LOCK_TABLE_FILE = "ocfl_object_lock.ddl.tmpl";
private static final String OBJECT_DETAILS_TABLE_FILE = "ocfl_object_details.ddl.tmpl";

private Map<DbType, String> dbScriptDir = Map.of(
private final Map<DbType, String> dbScriptDir = Map.of(
DbType.POSTGRES, "db/postgresql",
DbType.H2, "db/h2"
);

private DbType dbType;
private DataSource dataSource;
private final DbType dbType;
private final DataSource dataSource;

public TableCreator(DbType dbType, DataSource dataSource) {
this.dbType = Enforce.notNull(dbType, "dbType cannot be null");
this.dataSource = Enforce.notNull(dataSource, "dataSource cannot be null");
}

public void createObjectLockTable() {
createTable(LOCK_TABLE_FILE);
public void createObjectLockTable(String tableName) {
createTable(tableName, LOCK_TABLE_FILE);
}

public void createObjectDetailsTable() {
createTable(OBJECT_DETAILS_TABLE_FILE);
public void createObjectDetailsTable(String tableName) {
createTable(tableName, OBJECT_DETAILS_TABLE_FILE);
}

private void createTable(String fileName) {
private void createTable(String tableName, String fileName) {
Enforce.notBlank(tableName, "tableName cannot be blank");
try (var connection = dataSource.getConnection()) {
var filePath = getSqlFilePath(fileName);
LOG.debug("Loading {}", filePath);
if (filePath != null) {
try (var stream = this.getClass().getResourceAsStream("/" + filePath)) {
try (var statement = connection.prepareStatement(streamToString(stream))) {
var ddlTemplate = streamToString(stream);
var ddl = String.format(ddlTemplate, tableName);
try (var statement = connection.prepareStatement(ddl)) {
statement.executeUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,22 @@ public class H2ObjectLock implements ObjectLock {

private static final String OBJECT_LOCK_FAIL = "HYT00";

private DataSource dataSource;
private long waitMillis;
private final String tableName;
private final DataSource dataSource;
private final long waitMillis;

public H2ObjectLock(DataSource dataSource, long waitTime, TimeUnit timeUnit) {
private final String createRowLockQuery;
private final String acquireLockQuery;

public H2ObjectLock(String tableName, DataSource dataSource, long waitTime, TimeUnit timeUnit) {
this.tableName = Enforce.notBlank(tableName, "tableName cannot be blank");
this.dataSource = Enforce.notNull(dataSource, "dataSource cannot be null");
Enforce.expressionTrue(waitTime > -1, waitTime, "waitTime cannot be negative");
Enforce.notNull(timeUnit, "timeUnit cannot be null");
this.waitMillis = timeUnit.toMillis(waitTime);

createRowLockQuery = String.format("MERGE INTO %s (object_id) VALUES (?)", tableName);
acquireLockQuery = String.format("SELECT object_id FROM %s WHERE object_id = ? FOR UPDATE", tableName);
}

/**
Expand Down Expand Up @@ -106,8 +114,7 @@ public <T> T doInWriteLock(String objectId, Callable<T> doInLock) {
}

private void createLockRow(String objectId, Connection connection) throws SQLException {
try (var statement = connection.prepareStatement("MERGE INTO ocfl_object_lock" +
" (object_id) VALUES (?)")) {
try (var statement = connection.prepareStatement(createRowLockQuery)) {
statement.setString(1, objectId);
statement.executeUpdate();
}
Expand All @@ -124,7 +131,7 @@ private LockException failedToAcquireLock(String objectId) {
}

private PreparedStatement acquireLock(Connection connection) throws SQLException {
return connection.prepareStatement("SELECT object_id FROM ocfl_object_lock WHERE object_id = ? FOR UPDATE");
return connection.prepareStatement(acquireLockQuery);
}

private void safeCleanup(Connection connection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import edu.wisc.library.ocfl.api.exception.OcflJavaException;
import edu.wisc.library.ocfl.api.util.Enforce;
import edu.wisc.library.ocfl.core.db.DbType;
import edu.wisc.library.ocfl.core.db.ObjectDetailsDatabaseBuilder;
import edu.wisc.library.ocfl.core.db.TableCreator;

import javax.sql.DataSource;
Expand All @@ -37,9 +38,12 @@
*/
public class ObjectLockBuilder {

private static final String DEFAULT_TABLE_NAME = "ocfl_object_lock";

private long waitTime;
private TimeUnit timeUnit;
private DataSource dataSource;
private String tableName;

public ObjectLockBuilder() {
waitTime = 10;
Expand Down Expand Up @@ -70,6 +74,17 @@ public ObjectLockBuilder dataSource(DataSource dataSource) {
return this;
}

/**
* Sets the name of the table to use for object locking. Default: ocfl_object_lock
*
* @param tableName the table name to use
* @return builder
*/
public ObjectLockBuilder tableName(String tableName) {
this.tableName = tableName;
return this;
}

/**
* Constructs a new {@link ObjectLock}. If a DataSource was set, then a DB lock is created; otherwise, an in-memory
* lock is used.
Expand All @@ -87,21 +102,23 @@ public ObjectLock build() {
private ObjectLock buildDbLock() {
Enforce.notNull(dataSource, "dataSource cannot be null");

var resolvedTableName = tableName == null ? DEFAULT_TABLE_NAME : tableName;

var dbType = DbType.fromDataSource(dataSource);
ObjectLock lock;

switch (dbType) {
case POSTGRES:
lock = new PostgresObjectLock(dataSource, waitTime, timeUnit);
lock = new PostgresObjectLock(resolvedTableName, dataSource, waitTime, timeUnit);
break;
case H2:
lock = new H2ObjectLock(dataSource, waitTime, timeUnit);
lock = new H2ObjectLock(resolvedTableName, dataSource, waitTime, timeUnit);
break;
default:
throw new OcflJavaException(String.format("Database type %s is not mapped to an ObjectLock implementation.", dbType));
}

new TableCreator(dbType, dataSource).createObjectLockTable();
new TableCreator(dbType, dataSource).createObjectLockTable(resolvedTableName);

return lock;
}
Expand Down
Loading

0 comments on commit 8f65d51

Please sign in to comment.