diff --git a/data-migration/pom.xml b/data-migration/pom.xml index 81492fb20a..902d8dd8ed 100644 --- a/data-migration/pom.xml +++ b/data-migration/pom.xml @@ -9,6 +9,17 @@ data-migration jar + + + + + com.mockrunner + mockrunner-jdbc + 2.0.1 + test + + + @@ -31,7 +42,7 @@ org.xerial sqlite-jdbc - compile + runtime @@ -44,7 +55,7 @@ com.h2database h2 - compile + runtime @@ -58,6 +69,13 @@ system-rules test + + + + com.mockrunner + mockrunner-jdbc + test + diff --git a/data-migration/src/main/java/com/quorum/tessera/data/migration/CmdLineExecutor.java b/data-migration/src/main/java/com/quorum/tessera/data/migration/CmdLineExecutor.java index 7a77d49046..c937bc58ce 100644 --- a/data-migration/src/main/java/com/quorum/tessera/data/migration/CmdLineExecutor.java +++ b/data-migration/src/main/java/com/quorum/tessera/data/migration/CmdLineExecutor.java @@ -1,27 +1,29 @@ - package com.quorum.tessera.data.migration; +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.Properties; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingOptionException; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; - public class CmdLineExecutor { private CmdLineExecutor() { throw new UnsupportedOperationException(""); } - - protected static int execute(String... args) throws Exception { + protected static int execute(String... args) throws Exception { Options options = new Options(); @@ -47,6 +49,8 @@ protected static int execute(String... args) throws Exception { .required() .build()); + + options.addOption( Option.builder() .longOpt("exporttype") @@ -57,6 +61,16 @@ protected static int execute(String... args) throws Exception { .argName("TYPE") .required() .build()); + + options.addOption( + Option.builder() + .longOpt("dbconfig") + .desc("Properties file with create table, insert row and jdbc url") + .hasArg(true) + .optionalArg(false) + .numberOfArgs(1) + .argName("PATH") + .build()); options.addOption( Option.builder() @@ -71,25 +85,27 @@ protected static int execute(String... args) throws Exception { options.addOption( Option.builder() - .longOpt("dbuser") - .desc("Database username to use") - .hasArg(true) - .optionalArg(true) - .numberOfArgs(1) - .argName("PATH") - .required() - .build()); + .longOpt("dbuser") + .desc("Database username to use") + .hasArg(true) + .optionalArg(true) + .numberOfArgs(1) + .argName("USER") + .required() + .build()); options.addOption( Option.builder() - .longOpt("dbpass") - .desc("Database password to use") - .hasArg(true) - .optionalArg(true) - .numberOfArgs(1) - .argName("PATH") - .required() - .build()); + .longOpt("dbpass") + .desc("Database password to use") + .hasArg(true) + .optionalArg(true) + .numberOfArgs(1) + .argName("PASS") + .required() + .build()); + + if (Arrays.asList(args).contains("help")) { HelpFormatter formatter = new HelpFormatter(); @@ -110,10 +126,41 @@ protected static int execute(String... args) throws Exception { final String password = line.getOptionValue("dbpass"); final String exportTypeStr = line.getOptionValue("exporttype"); - final ExportType exportType = ExportType.valueOf(exportTypeStr.toUpperCase()); + + final ExportType exportType = Optional.ofNullable(exportTypeStr) + .map(String::toUpperCase) + .map(ExportType::valueOf).get(); final Path outputFile = Paths.get(line.getOptionValue("outputfile")).toAbsolutePath(); - final DataExporter dataExporter = DataExporterFactory.create(exportType); + + final DataExporter dataExporter; + if (exportType == ExportType.JDBC) { + if (!line.hasOption("dbconfig")) { + throw new MissingOptionException("dbconfig file path is required when no export type is defined."); + } + + String dbconfig = line.getOptionValue("dbconfig"); + + Properties properties = new Properties(); + try (InputStream inStream = Files.newInputStream(Paths.get(dbconfig))) { + properties.load(inStream); + } + + String insertRow = Objects.requireNonNull(properties.getProperty("insertRow",null), + "No insertRow value defined in config file. "); + + String createTable = Objects.requireNonNull(properties.getProperty("createTable",null), + "No createTable value defined in config file. "); + + String jdbcUrl = Objects.requireNonNull(properties.getProperty("jdbcUrl",null), + "No jdbcUrl value defined in config file. "); + + dataExporter = new JdbcDataExporter(jdbcUrl, insertRow, createTable); + + } else { + dataExporter = DataExporterFactory.create(exportType); + } + dataExporter.export(data, outputFile, username, password); System.out.printf("Exported data to %s", Objects.toString(outputFile)); diff --git a/data-migration/src/main/java/com/quorum/tessera/data/migration/ExportType.java b/data-migration/src/main/java/com/quorum/tessera/data/migration/ExportType.java index d25b1cfa38..9d16e7a188 100644 --- a/data-migration/src/main/java/com/quorum/tessera/data/migration/ExportType.java +++ b/data-migration/src/main/java/com/quorum/tessera/data/migration/ExportType.java @@ -1,19 +1,11 @@ package com.quorum.tessera.data.migration; -import java.sql.Driver; - public enum ExportType { - H2(org.h2.Driver.class), - SQLITE(org.sqlite.JDBC.class); - - final Class driver; + H2, + SQLITE, + JDBC; - ExportType(Class driver) { - this.driver = driver; - } - - } diff --git a/data-migration/src/main/java/com/quorum/tessera/data/migration/JdbcDataExporter.java b/data-migration/src/main/java/com/quorum/tessera/data/migration/JdbcDataExporter.java new file mode 100644 index 0000000000..fe9547f3f9 --- /dev/null +++ b/data-migration/src/main/java/com/quorum/tessera/data/migration/JdbcDataExporter.java @@ -0,0 +1,46 @@ +package com.quorum.tessera.data.migration; + +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.Map.Entry; + +public class JdbcDataExporter implements DataExporter { + + private final String jdbcUrl; + + private final String insertRow; + + private final String createTable; + + public JdbcDataExporter(String jdbcUrl, String insertRow, String createTable) { + this.jdbcUrl = jdbcUrl; + this.insertRow = insertRow; + this.createTable = createTable; + } + + @Override + public void export(Map data, Path output, String username, String password) throws SQLException { + + try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) { + + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(createTable); + } + + try (PreparedStatement insertStatement = conn.prepareStatement(insertRow)) { + for (Entry values : data.entrySet()) { + insertStatement.setBytes(1, values.getKey()); + insertStatement.setBytes(2, values.getValue()); + insertStatement.execute(); + } + } + + } + } + +} diff --git a/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java b/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java index acccf3c250..dd0f3cd37e 100644 --- a/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java +++ b/data-migration/src/test/java/com/quorum/tessera/data/migration/CmdLineExecutorTest.java @@ -1,5 +1,6 @@ package com.quorum.tessera.data.migration; +import com.mockrunner.mock.jdbc.JDBCMockObjectFactory; import org.apache.commons.cli.MissingOptionException; import org.junit.After; import org.junit.Before; @@ -36,9 +37,9 @@ public void onSetup() throws Exception { public void onTearDown() throws IOException { if (Files.exists(outputPath)) { Files.walk(outputPath) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); } } @@ -60,7 +61,7 @@ public void noOptions() { assertThat(throwable).isInstanceOf(MissingOptionException.class); assertThat(((MissingOptionException) throwable).getMissingOptions()) - .containsExactlyInAnyOrder("storetype", "inputpath", "exporttype", "outputfile", "dbpass", "dbuser"); + .containsExactlyInAnyOrder("storetype", "inputpath", "exporttype", "outputfile", "dbpass", "dbuser"); } @@ -150,8 +151,53 @@ public void cannotBeConstructed() throws Exception { final Throwable throwable = catchThrowable(constructor::newInstance); assertThat(throwable) - .isInstanceOf(InvocationTargetException.class) - .hasCauseExactlyInstanceOf(UnsupportedOperationException.class); + .isInstanceOf(InvocationTargetException.class) + .hasCauseExactlyInstanceOf(UnsupportedOperationException.class); } + @Test(expected = org.apache.commons.cli.MissingOptionException.class) + public void exportTypeJdbcNoDbConfigProvided() throws Exception { + + final Path inputFile = Paths.get(getClass().getResource("/dir/").toURI()); + + final String[] args = new String[]{ + "-storetype", "dir", + "-inputpath", inputFile.toString(), + "-outputfile", outputPath.toString(), + "-exporttype", "jdbc", + "-dbpass", "-dbuser" + }; + + CmdLineExecutor.execute(args); + + } + + @Test + public void exportTypeJdbc() throws Exception { + + JDBCMockObjectFactory mockObjectFactory = new JDBCMockObjectFactory(); + + try { + mockObjectFactory.registerMockDriver(); + + String dbConfigPath = getClass().getResource("/dbconfig.properties").getFile(); + + final Path inputFile = Paths.get(getClass().getResource("/dir/").toURI()); + + final String[] args = new String[]{ + "-storetype", "dir", + "-inputpath", inputFile.toString(), + "-outputfile", outputPath.toString(), + "-exporttype", "jdbc", + "-dbconfig", dbConfigPath, + "-dbpass", "-dbuser" + }; + + CmdLineExecutor.execute(args); + + assertThat(outputPath).isNotNull(); + } finally { + mockObjectFactory.restoreDrivers(); + } + } } diff --git a/data-migration/src/test/java/com/quorum/tessera/data/migration/JdbcDataExporterTest.java b/data-migration/src/test/java/com/quorum/tessera/data/migration/JdbcDataExporterTest.java new file mode 100644 index 0000000000..befc12c60e --- /dev/null +++ b/data-migration/src/test/java/com/quorum/tessera/data/migration/JdbcDataExporterTest.java @@ -0,0 +1,51 @@ + +package com.quorum.tessera.data.migration; + +import com.mockrunner.jdbc.BasicJDBCTestCaseAdapter; +import com.mockrunner.mock.jdbc.JDBCMockObjectFactory; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Test; +import static org.mockito.Mockito.mock; + + +public class JdbcDataExporterTest extends BasicJDBCTestCaseAdapter{ + + + private final JDBCMockObjectFactory mockObjectFactory = new JDBCMockObjectFactory(); + + @Test + public void onSetUp() { + mockObjectFactory.registerMockDriver(); + } + + @After + public void onTearDown(){ + mockObjectFactory.restoreDrivers(); + } + + @Test + public void doStuff() throws Exception { + + + JdbcDataExporter exporter = new JdbcDataExporter("jdbc:bogus","insert stuff","create stuff"); + + Map data = new HashMap() {{ + put("ONE".getBytes(),"TWO".getBytes()); + }}; + + + + Path output = mock(Path.class); + + exporter.export(data, output, "someone", "pw"); + + verifyAllStatementsClosed(); + verifyAllStatementsClosed(); + + + } + +} diff --git a/data-migration/src/test/resources/dbconfig.properties b/data-migration/src/test/resources/dbconfig.properties new file mode 100644 index 0000000000..85b91273e1 --- /dev/null +++ b/data-migration/src/test/resources/dbconfig.properties @@ -0,0 +1,3 @@ +insertRow=insert stuff +createTable=create stuff +jdbcUrl=jdbc:bogus