diff --git a/.gitignore b/.gitignore index 279a9183f..e58913106 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /webapp/config/debug.log4j.properties .build utilities/solrtester/.work +utilities/rdbmigration/.work diff --git a/utilities/rdbmigration/build.xml b/utilities/rdbmigration/build.xml new file mode 100644 index 000000000..6071ab50b --- /dev/null +++ b/utilities/rdbmigration/build.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + all - Compiles and runs the RDB migration tool. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utilities/rdbmigration/lib/arq-2.8.5.jar b/utilities/rdbmigration/lib/arq-2.8.5.jar new file mode 100644 index 000000000..f89b6937e Binary files /dev/null and b/utilities/rdbmigration/lib/arq-2.8.5.jar differ diff --git a/utilities/rdbmigration/lib/commons-lang-2.6.jar b/utilities/rdbmigration/lib/commons-lang-2.6.jar new file mode 100644 index 000000000..98467d3a6 Binary files /dev/null and b/utilities/rdbmigration/lib/commons-lang-2.6.jar differ diff --git a/utilities/rdbmigration/lib/icu4j-3.4.4.jar b/utilities/rdbmigration/lib/icu4j-3.4.4.jar new file mode 100644 index 000000000..f5e8c167e Binary files /dev/null and b/utilities/rdbmigration/lib/icu4j-3.4.4.jar differ diff --git a/utilities/rdbmigration/lib/iri-0.8.jar b/utilities/rdbmigration/lib/iri-0.8.jar new file mode 100644 index 000000000..f096c689c Binary files /dev/null and b/utilities/rdbmigration/lib/iri-0.8.jar differ diff --git a/utilities/rdbmigration/lib/jena-2.6.4.jar b/utilities/rdbmigration/lib/jena-2.6.4.jar new file mode 100644 index 000000000..efc64a94f Binary files /dev/null and b/utilities/rdbmigration/lib/jena-2.6.4.jar differ diff --git a/utilities/rdbmigration/lib/log4j-1.2.14.jar b/utilities/rdbmigration/lib/log4j-1.2.14.jar new file mode 100644 index 000000000..625130719 Binary files /dev/null and b/utilities/rdbmigration/lib/log4j-1.2.14.jar differ diff --git a/utilities/rdbmigration/lib/mysql-connector-java-5.1.16-bin.jar b/utilities/rdbmigration/lib/mysql-connector-java-5.1.16-bin.jar new file mode 100644 index 000000000..e62f2cb77 Binary files /dev/null and b/utilities/rdbmigration/lib/mysql-connector-java-5.1.16-bin.jar differ diff --git a/utilities/rdbmigration/lib/slf4j-api-1.5.6.jar b/utilities/rdbmigration/lib/slf4j-api-1.5.6.jar new file mode 100644 index 000000000..9b4221694 Binary files /dev/null and b/utilities/rdbmigration/lib/slf4j-api-1.5.6.jar differ diff --git a/utilities/rdbmigration/lib/slf4j-log4j12-1.5.6.jar b/utilities/rdbmigration/lib/slf4j-log4j12-1.5.6.jar new file mode 100644 index 000000000..0c40e9c8b Binary files /dev/null and b/utilities/rdbmigration/lib/slf4j-log4j12-1.5.6.jar differ diff --git a/utilities/rdbmigration/lib/tdb-0.8.7.jar b/utilities/rdbmigration/lib/tdb-0.8.7.jar new file mode 100644 index 000000000..5b7965d65 Binary files /dev/null and b/utilities/rdbmigration/lib/tdb-0.8.7.jar differ diff --git a/utilities/rdbmigration/lib/xercesImpl.jar b/utilities/rdbmigration/lib/xercesImpl.jar new file mode 100644 index 000000000..eac75ae8e Binary files /dev/null and b/utilities/rdbmigration/lib/xercesImpl.jar differ diff --git a/utilities/rdbmigration/src/edu/cornell/mannlib/vivo/utilities/rdbmigration/RdbMigrator.java b/utilities/rdbmigration/src/edu/cornell/mannlib/vivo/utilities/rdbmigration/RdbMigrator.java new file mode 100644 index 000000000..cf7ac8610 --- /dev/null +++ b/utilities/rdbmigration/src/edu/cornell/mannlib/vivo/utilities/rdbmigration/RdbMigrator.java @@ -0,0 +1,361 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vivo.utilities.rdbmigration; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.lang.StringUtils; + +import com.hp.hpl.jena.db.DBConnection; +import com.hp.hpl.jena.db.GraphRDB; +import com.hp.hpl.jena.graph.Node; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.sparql.core.DatasetGraph; +import com.hp.hpl.jena.tdb.TDBFactory; + +/** + * A free-standing application that walks the user through the process of + * copying VIVO configuration data from RDB to TDB. + */ +public class RdbMigrator { + private static final String TABLE_RDB = "jena_graph"; + private static final String TABLE_MIGRATED = "vivo_rdb_migrated"; + private static final List EXPECTED_MODELS = Arrays + .asList(new String[] { + "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata", + "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadata-displayModel", + "http://vitro.mannlib.cornell.edu/default/vitro-kb-displayMetadataTBOX", + "http://vitro.mannlib.cornell.edu/default/vitro-kb-userAccounts" }); + + private final String vivoHomeDir; + private final String jdbcUrl; + private final String username; + private final String password; + + private File targetDir; + private boolean alreadyMigrated; + private List modelsToCopy; + + /** + * Confirm all of the parameters. Ask the user for approval. Do the + * migration. + * + * @throws UserDeclinedException + * If the user decides not to continue. + */ + public RdbMigrator(String vivoHomeDir, String jdbcUrl, String username, + String password) throws UserDeclinedException, IOException, + SQLException { + this.vivoHomeDir = vivoHomeDir; + this.jdbcUrl = jdbcUrl; + this.username = username; + this.password = password; + + testDbConnection(); + + checkThatRdbExists(); + + confirmTargetDirectory(); + + if (doesTdbExist()) { + askContinueOverTdb(); + } + + if (isAlreadyMigrated()) { + askMigrateAgain(); + } + + getListOfRdbModels(); + + if (isUnexpectedModels()) { + askMigrateAllModels(); + } + + askApprovalForMigrationPlan(); + + migrate(); + } + + private void testDbConnection() { + try (Connection conn = getSqlConnection()) { + // Just open and close it. + } catch (SQLException e) { + quit("Can't log in to database: '" + jdbcUrl + "', '" + username + + "', '" + password + "'\n" + e.getMessage()); + } + } + + private void checkThatRdbExists() throws SQLException { + try (Connection conn = getSqlConnection()) { + DatabaseMetaData md = conn.getMetaData(); + try (ResultSet rs = md.getTables(null, null, TABLE_RDB, null);) { + if (!rs.next()) { + quit("The database at '" + jdbcUrl + + "' contains no RDB tables."); + } + } + } + } + + private void confirmTargetDirectory() { + File vivoHome = new File(vivoHomeDir); + if (!vivoHome.isDirectory()) { + quit("'" + vivoHome + "' is not a directory."); + } + if (!vivoHome.canWrite()) { + quit("Can't write to '" + vivoHome + "'."); + } + targetDir = new File(vivoHome, "tdbmodels"); + } + + private boolean doesTdbExist() { + if (!targetDir.exists()) { + return false; + } + + if (!targetDir.isDirectory()) { + quit("'" + targetDir + "' is not a directory."); + } + if (!targetDir.canWrite()) { + quit("Can't write to '" + targetDir + "'."); + } + + String[] filenames = targetDir.list(); + if (filenames == null || filenames.length == 0) { + return false; + } + + return true; + } + + private void askContinueOverTdb() throws UserDeclinedException, IOException { + ask("A directory of TDB files exists at '" + targetDir + "'.\n" + + " Migration will replace the existing triples.\n" + + "Continue? (y/n)"); + } + + private boolean isAlreadyMigrated() throws SQLException { + try (Connection conn = getSqlConnection()) { + DatabaseMetaData md = conn.getMetaData(); + try (ResultSet rs = md.getTables(null, null, TABLE_MIGRATED, null);) { + if (rs.next()) { + alreadyMigrated = true; + announceMigrationDate(conn); + return true; + } else { + return false; + } + } + } + } + + private void announceMigrationDate(Connection conn) { + String migrationDate = "UNKNOWN DATE"; + String query = String.format("SELECT date FROM %s LIMIT 1", + TABLE_MIGRATED); + + try (Statement stmt = conn.createStatement(); + java.sql.ResultSet rs = stmt.executeQuery(query)) { + if (rs.next()) { + migrationDate = rs.getString("DATE"); + } + } catch (SQLException e) { + // go with default answer. + } + + System.out.println("It looks like this RDB data has already been " + + "migrated to TDB, on " + migrationDate + + "\n (found a table named '" + TABLE_MIGRATED + "')"); + } + + private void askMigrateAgain() throws UserDeclinedException, IOException { + ask("Migrate again? (y/n)"); + } + + private void getListOfRdbModels() throws SQLException { + try (Connection conn = getSqlConnection(); + ClosingDBConnection rdb = new ClosingDBConnection(conn)) { + modelsToCopy = rdb.getAllModelNames().toList(); + } + } + + private boolean isUnexpectedModels() { + List unexpectedModels = new ArrayList<>(modelsToCopy); + unexpectedModels.removeAll(EXPECTED_MODELS); + if (unexpectedModels.isEmpty()) { + return false; + } + + System.out + .println("VIVO requires only these models from RDB:\n " + + StringUtils.join(EXPECTED_MODELS, "\n ") + + "\nYour RDB triple-store contains these additional models:\n " + + StringUtils.join(unexpectedModels, "\n ")); + return true; + } + + private void askMigrateAllModels() throws IOException, UserDeclinedException { + System.out.println("Migrate all models, or only the required models?\n" + + " Enter 'all' or 'only', or anything else to quit."); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String s = br.readLine(); + if (s != null) { + if (s.trim().toLowerCase().equals("all")) { + return; + } else if (s.trim().toLowerCase().equals("only")) { + modelsToCopy = EXPECTED_MODELS; + return; + } + } + throw new UserDeclinedException("Enter 'all' or 'only'."); + } + + private void askApprovalForMigrationPlan() throws SQLException, + UserDeclinedException, IOException { + int modelCount = 0; + int tripleCount = 0; + try (Connection conn = getSqlConnection(); + ClosingDBConnection rdb = new ClosingDBConnection(conn)) { + for (String modelName : modelsToCopy) { + modelCount++; + try (ClosingGraphRDB graph = new ClosingGraphRDB(rdb, modelName)) { + tripleCount += graph.size(); + } + } + } + String warning = alreadyMigrated ? " Existing triples will be over-written.\n" + : ""; + String question = String.format("Migrating %d triples in %d models " + + "to TDB files in '%s'\n%sContinue? (y/n)", tripleCount, + modelCount, targetDir, warning); + ask(question); + } + + private void quit(String message) { + throw new IllegalArgumentException( + "--------------------------------------------------------------\n" + + message + + "\n--------------------------------------------------------------"); + } + + private void ask(String string) throws UserDeclinedException, IOException { + System.out.println(string); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String s = br.readLine(); + if ((s == null) || (!s.trim().toLowerCase().equals("y"))) { + throw new UserDeclinedException("OK."); + } + } + + private static class UserDeclinedException extends Exception { + public UserDeclinedException(String message) { + super(message); + } + } + + public void migrate() throws SQLException { + copyData(); + writeMigrationRecord(); + } + + private void copyData() throws SQLException { + try (Connection conn = getSqlConnection(); + ClosingDBConnection rdb = new ClosingDBConnection(conn)) { + Dataset tdbDataset = TDBFactory.createDataset(targetDir + .getAbsolutePath()); + copyGraphs(rdb, tdbDataset); + } + } + + @SuppressWarnings("deprecation") + private void copyGraphs(ClosingDBConnection rdb, Dataset tdbDataset) { + DatasetGraph tdbDsGraph = tdbDataset.asDatasetGraph(); + for (String modelName : modelsToCopy) { + try (ClosingGraphRDB graph = new ClosingGraphRDB(rdb, modelName)) { + tdbDsGraph.addGraph(Node.createURI(modelName), graph); + System.out + .println(String.format(" copied %4d triples from %s", + graph.size(), modelName)); + } + } + } + + private void writeMigrationRecord() throws SQLException { + String createTable = String.format("CREATE TABLE %s (date DATE)", + TABLE_MIGRATED); + String deleteOldDates = String.format("DELETE FROM %s", TABLE_MIGRATED); + String insertDate = String.format("INSERT INTO %s (date) VALUES (?)", + TABLE_MIGRATED); + try (Connection conn = getSqlConnection(); + Statement stmt = conn.createStatement(); + PreparedStatement pstmt = conn.prepareStatement(insertDate)) { + if (alreadyMigrated) { + stmt.executeUpdate(deleteOldDates); + } else { + stmt.executeUpdate(createTable); + } + pstmt.setDate(1, new Date(System.currentTimeMillis())); + pstmt.executeUpdate(); + } + } + + private Connection getSqlConnection() throws SQLException { + Properties connectionProps; + connectionProps = new Properties(); + connectionProps.put("user", username); + connectionProps.put("password", password); + return DriverManager.getConnection(jdbcUrl, connectionProps); + } + + private static class ClosingDBConnection extends DBConnection implements + AutoCloseable { + ClosingDBConnection(Connection sqlConnection) { + super(sqlConnection, "MySQL"); + } + + } + + private static class ClosingGraphRDB extends GraphRDB implements + AutoCloseable { + ClosingGraphRDB(DBConnection rdb, String modelName) { + super(rdb, modelName, null, + GraphRDB.OPTIMIZE_ALL_REIFICATIONS_AND_HIDE_NOTHING, false); + } + } + + // ---------------------------------------------------------------------- + // Main routine + // ---------------------------------------------------------------------- + + @SuppressWarnings("unused") + public static void main(String[] args) { + if (args.length != 4) { + System.out.println("Usage: RdbMigrator vivoHomeDir, jdbcUrl, " + + "username, password"); + } + + try { + new RdbMigrator(args[0], args[1], args[2], args[3]); + } catch (IllegalArgumentException | UserDeclinedException e) { + System.out.println(e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/webapp/build.xml b/webapp/build.xml index 61b2d24e4..479a66e0b 100644 --- a/webapp/build.xml +++ b/webapp/build.xml @@ -579,6 +579,16 @@ deploy - Deploy the application directly into the Tomcat webapps directory. + + + + + + + +