Skip to content

Commit

Permalink
Restructuring database configuration. Work in process, but unit and i…
Browse files Browse the repository at this point in the history
…ntegration tests all OK
  • Loading branch information
janblom committed Nov 21, 2023
1 parent 3b963b8 commit e19472a
Show file tree
Hide file tree
Showing 10 changed files with 539 additions and 118 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Requires Java 1.8 or higher, and read access to the database to be scanned. Java

Dependencies
============
For the distributable packages, the only requirement is Java 8. For building the package, also Maven is needed.
For the distributable packages, the only requirement is Java 8. For building the package, Java 17 and Maven are needed.

Getting Started
===============
Expand Down Expand Up @@ -91,15 +91,17 @@ Development
White Rabbit and Rabbit in a Hat are structured as a Maven package and can be developed in Eclipse. Contributions are welcome.

While the software in the project can be executed with Java 1.8, for development Java 17 is needed.
This has to do with test and verification dependencies that are not available in
a version compatible with Java 1.8 .
This has to do with test and verification dependencies that are not available in a version compatible with Java 1.8 .

Please note that when using an IDE for development, source and target release must still be Java 1.8 . This is enforced
in the maven build file (pom.xml),

To generate the files ready for distribution, run `mvn install`.

### Testing

A limited number of unit and integration tests exist. The integration tests run only in the `maven verify` phase,
and depend on docker being available to the user running the verification. If docker is not available, the
A limited number of unit and integration tests exist. The integration tests run only in the maven verification phase,
(`mn verify`) and depend on docker being available to the user running the verification. If docker is not available, the
integration tests will fail.

Also, GitHub actions have been configured to run the test suite automatically.
Expand All @@ -119,7 +121,7 @@ is provided through environment variables:

It is recommended that user, password, database and schema are created for these tests only,
and do not relate in any way to any production environment.
The schema should not contain any tables.
The schema should not contain any tables when the test is started.

It is possible to skip the Snowflake tests without failing the build by passing
`-Dohdsi.org.whiterabbit.skip_snowflake_tests=1` to maven.
Expand Down
194 changes: 194 additions & 0 deletions rabbit-core/src/main/java/org/ohdsi/databases/DBConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package org.ohdsi.databases;

import org.apache.commons.lang.StringUtils;
import org.ohdsi.utilities.files.IniFile;

import java.io.PrintStream;
import java.util.*;

public abstract class DBConfiguration {
public static final String ERROR_DUPLICATE_DEFINITIONS_FOR_FIELD = "Multiple definitions for field ";
private IniFile iniFile;
private ConfigurationFields configurationFields;

private DBConfiguration() {}
public DBConfiguration(IniFile inifile) {
this.iniFile = inifile;
}

protected DBConfiguration(ConfigurationField... fields) {
checkForDuplicates(fields);
this.configurationFields = new ConfigurationFields(fields);
}

private void checkForDuplicates(ConfigurationField... fields) {
Set<String> names = new HashSet<>();
for (ConfigurationField field: fields) {
if (names.contains(field.name)) {
throw new DBConfigurationException(ERROR_DUPLICATE_DEFINITIONS_FOR_FIELD + field.name);
}
names.add(field.name);
}
}
public ValidationFeedback validate() {
ValidationFeedback configurationFeedback = new ValidationFeedback();
for (ConfigurationField field: this.getFields()) {
for (FieldValidator validator: field.validators) {
ValidationFeedback feedback = validator.validate(field);
configurationFeedback.addWarnings(feedback.getWarnings());
configurationFeedback.addErrors(feedback.getErrors());
}
}

return configurationFeedback;
}

public List<ConfigurationField> getFields() {
return configurationFields.getFields();
}
public void printIniFileTemplate(PrintStream stream) {
for (ConfigurationField field: this.configurationFields.getFields()) {
stream.printf("%s: %s\t%s%n",
field.name,
StringUtils.isEmpty(field.getDefaultValue()) ? "_" : field.getDefaultValue(),
field.toolTip);
}
}
public interface FieldSet {
List<ConfigurationField> getFields();

default void generateIniFileFormat() {
for (ConfigurationField field: getFields()) {
System.out.println(String.format("%s:\t___\t# %s", field.name, field.toolTip));
}
}
}

public static class DBConfigurationException extends RuntimeException {
public DBConfigurationException(String s) {
super(s);
}
};

@FunctionalInterface
public interface FieldValidator {
ValidationFeedback validate(ConfigurationField field);
}

@FunctionalInterface
public interface ConfigurationValidator {
ValidationFeedback validate(ConfigurationFields fields);
}

public static class ValidationFeedback {
private List<String> warnings = new ArrayList<>();
private List<String> errors = new ArrayList<>();

public boolean isFullyValid() {
return warnings.isEmpty() && errors.isEmpty();
}

public boolean hasWarnings() {
return !warnings.isEmpty();
}

public boolean hasErrors() {
return !errors.isEmpty();
}

public List<String> getWarnings() {
return this.warnings;
}

public List<String> getErrors() {
return this.errors;
}

public void addWarning(String warning) {
this.warnings.add(warning);
}
public void addWarnings(List<String> warnings) {
this.warnings.addAll(warnings);
}
public void addError(String error) {
this.errors.add(error);
}
public void addErrors(List<String> errors) {
this.errors.addAll(errors);
}
}

public static class ConfigurationFields implements FieldSet {
List<ConfigurationField> fields;

public ConfigurationFields(ConfigurationField... fields) {
this.fields = new ArrayList<>(Arrays.asList(fields));
}

public List<ConfigurationField> getFields() {
return this.fields;
}
}

public static class ConfigurationField {
public final String name;
public final String label;
public final String toolTip;
private String value;
private String defaultValue;

public static final String VALUE_REQUIRED_FORMAT_STRING = "A non-empty value is required for field %s (name %s)";
private List<FieldValidator> validators = new ArrayList<>();

private static FieldValidator fieldRequiredValidator = new FieldRequiredValidator();

private ConfigurationField(String name, String label, String toolTip) {
this.name = name;
this.label = label;
this.toolTip = toolTip;
this.defaultValue = "";
}

public static ConfigurationField create(String name, String label, String toolTip) {
return new ConfigurationField(name, label, toolTip);
}

public ConfigurationField required() {
this.addValidator(fieldRequiredValidator);
return this;
}

public ConfigurationField addDefaultValue(String value) {
this.defaultValue = value;
return this;
}

public ConfigurationField addValidator(FieldValidator validator) {
this.validators.add(validator);
return this;
}

public void setValue(String value) {
this.value = value;
}

public String getValue() {
return this.value;
}

public String getDefaultValue() {
return this.defaultValue;
}

private static class FieldRequiredValidator implements FieldValidator {
public ValidationFeedback validate(ConfigurationField field) {
ValidationFeedback feedback = new ValidationFeedback();
if (StringUtils.isEmpty(field.getValue())) {
feedback.addError(String.format(VALUE_REQUIRED_FORMAT_STRING, field.label, field.name));
}

return feedback;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public interface DBConnectorInterface {
void checkInitialised();

DBConnectorInterface getInstance();
default public DBConnectorInterface getInstance(DbSettings dbSettings) {
return getInstance(dbSettings.server, dbSettings.database, dbSettings.user, dbSettings.password);
}
// default DBConnectorInterface getInstance(DbSettings dbSettings) {
// return getInstance(dbSettings.server, dbSettings.database, dbSettings.user, dbSettings.password);
// }
DBConnectorInterface getInstance(String server, String database, String user, String password);
/**
* Returns the row count of the specified table.
Expand Down Expand Up @@ -76,4 +76,5 @@ static DBConnectorInterface getDBConnectorInstance(IniFile iniFile) {

DbSettings getDbSettings();

public List<DBConfiguration.ConfigurationField> getFields();
}
Loading

0 comments on commit e19472a

Please sign in to comment.