Skip to content

Commit

Permalink
Simplify integrity check. Small Refactorings.
Browse files Browse the repository at this point in the history
  • Loading branch information
obraliar committed Jun 3, 2016
1 parent 4342c7d commit d47d740
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 425 deletions.
148 changes: 55 additions & 93 deletions src/main/java/net/sf/jabref/remote/DBProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.jabref.event.location.EntryEventLocation;
Expand All @@ -46,12 +44,15 @@ public class DBProcessor {
private DBType dbType;
private final DBHelper dbHelper;

// Elected name for main table
public static final String ENTRY = "ENTRY";

public static final List<String> ALL_TABLES = new ArrayList<>(Arrays.asList(ENTRY));

// Elected column names of main the table
// This entries are needed to ease the changeability, cause some database systems dependent on the context expect low or uppercase characters.
public static final String REMOTE_ID = "REMOTE_ID";
public static final String ENTRYTYPE = "ENTRYTYPE";
public static final String ENTRY_REMOTE_ID = "REMOTE_ID";
public static final String ENTRY_ENTRYTYPE = "ENTRYTYPE";



/**
Expand All @@ -65,88 +66,58 @@ public DBProcessor(Connection connection, DBType dbType) {
}

/**
* Scans the structure of the main table and checks it.
* Scans the database for required tables.
* @return <code>true</code> if the structure matches the requirements, <code>false</code> if not.
*/
public boolean checkIntegrity() {
Map<String, String> requiredColumns = dbType.getStructure(ENTRY); //get appropriate column names and their types

public boolean checkBaseIntegrity() {
List<String> requiredTables = new ArrayList<>(ALL_TABLES);
try {
DatabaseMetaData databaseMetaData = connection.getMetaData();

// ...getTables(null, ...): no restrictions
try (ResultSet databaseMetaDataResultSet = databaseMetaData.getTables(null, null, null, null)) {

Set<String> requiredTables = new HashSet<>(); // Back door for new tables
requiredTables.add(ENTRY);

while (databaseMetaDataResultSet.next()) {
String tableName = databaseMetaDataResultSet.getString("TABLE_NAME").toUpperCase();
requiredTables.remove(tableName); // Remove matching tables to check requiredTables for emptiness
}
databaseMetaDataResultSet.close();

if (requiredTables.isEmpty()) {
try (ResultSet resultSet = dbHelper.query("SELECT * FROM " + escape(ENTRY, dbType))) {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // get structural data of the table

for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
// All databases together don't hold the names in the same character case
requiredColumns.remove(resultSetMetaData.getColumnName(i + 1).toUpperCase(),
resultSetMetaData.getColumnTypeName(i + 1).toUpperCase());
}

return requiredColumns.isEmpty();
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
}
}
databaseMetaDataResultSet.close();
return requiredTables.size() == 0;
}
} catch (SQLException e1) {
e1.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}

return false;
}

/**
* Creates and sets up the needed tables and columns according to the database type.
*/
public void setUpRemoteDatabase() {
try {
if (dbType == DBType.MYSQL) {
connection.createStatement().executeUpdate(
"CREATE TABLE IF NOT EXISTS " + ENTRY +" ("
+ REMOTE_ID + " INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,"
+ ENTRYTYPE + " VARCHAR(255) DEFAULT NULL"
if (dbType == DBType.MYSQL) {
executeUpdate("CREATE TABLE IF NOT EXISTS " + ENTRY + " ("
+ ENTRY_REMOTE_ID + " INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,"
+ ENTRY_ENTRYTYPE + " VARCHAR(255) DEFAULT NULL"
+ ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;");
} else if (dbType == DBType.POSTGRESQL) {
connection.createStatement().executeUpdate(
"CREATE TABLE IF NOT EXISTS " + ENTRY + " ("
+ REMOTE_ID + " SERIAL PRIMARY KEY,"
+ ENTRYTYPE + " VARCHAR);");
} else if (dbType == DBType.ORACLE) {
connection.createStatement().executeUpdate(
"CREATE TABLE \"" + ENTRY + "\" ("
+ "\"" + REMOTE_ID + "\" NUMBER NOT NULL,"
+ "\"" + ENTRYTYPE + "\" VARCHAR2(255) NULL,"
+ "CONSTRAINT \"ENTRY_PK\" PRIMARY KEY (\"" + REMOTE_ID + "\"))");
connection.createStatement().executeUpdate("CREATE SEQUENCE \"" + ENTRY + "_SEQ\"");
connection.createStatement().executeUpdate(
"CREATE TRIGGER \"BI_" + ENTRY + "\" BEFORE INSERT ON \"" + ENTRY + "\" "
+ "FOR EACH ROW BEGIN "
+ "SELECT \"" + ENTRY + "_SEQ\".NEXTVAL INTO :NEW." + REMOTE_ID.toLowerCase() + " FROM DUAL; "
+ "END;");
}
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
} else if (dbType == DBType.POSTGRESQL) {
executeUpdate("CREATE TABLE IF NOT EXISTS " + ENTRY + " ("
+ ENTRY_REMOTE_ID + " SERIAL PRIMARY KEY,"
+ ENTRY_ENTRYTYPE + " VARCHAR);");
} else if (dbType == DBType.ORACLE) {
executeUpdate("CREATE TABLE \"" + ENTRY + "\" (" + "\""
+ ENTRY_REMOTE_ID + "\" NUMBER NOT NULL," + "\""
+ ENTRY_ENTRYTYPE + "\" VARCHAR2(255) NULL,"
+ "CONSTRAINT \"ENTRY_PK\" PRIMARY KEY (\"" + ENTRY_REMOTE_ID + "\"))");
executeUpdate("CREATE SEQUENCE \"" + ENTRY + "_SEQ\"");
executeUpdate("CREATE TRIGGER \"BI_" + ENTRY + "\" BEFORE INSERT ON \"" + ENTRY + "\" "
+ "FOR EACH ROW BEGIN " + "SELECT \"" + ENTRY + "_SEQ\".NEXTVAL INTO :NEW."
+ ENTRY_REMOTE_ID.toLowerCase() + " FROM DUAL; " + "END;");
}

if (!checkIntegrity()) {
if (!checkBaseIntegrity()) {
// can only happen with users direct intervention in remote database
LOGGER.error(Localization.lang("Corrupt_remote_database_structure."));
}

}

/**
Expand All @@ -159,7 +130,7 @@ public void insertEntry(BibEntry bibEntry) {
// Check if already exists
int remote_id = bibEntry.getRemoteId();
if (remote_id != -1) {
try (ResultSet resultSet = dbHelper.query("SELECT * FROM "+ escape(ENTRY, dbType) +" WHERE "+ escape(REMOTE_ID, dbType) +" = " + remote_id)) {
try (ResultSet resultSet = dbHelper.query("SELECT * FROM "+ escape(ENTRY, dbType) +" WHERE "+ escape(ENTRY_REMOTE_ID, dbType) +" = " + remote_id)) {
if (resultSet.next()) {
return;
}
Expand All @@ -175,14 +146,14 @@ public void insertEntry(BibEntry bibEntry) {
for (int i = 0; i < fieldNames.size(); i++) {
query = query + escape(fieldNames.get(i).toUpperCase(), dbType) + ", ";
}
query = query + escape(ENTRYTYPE, dbType) + ") VALUES(";
query = query + escape(ENTRY_ENTRYTYPE, dbType) + ") VALUES(";
for (int i = 0; i < fieldNames.size(); i++) {
query = query + escapeValue(bibEntry.getField(fieldNames.get(i))) + ", ";
}
query = query + escapeValue(bibEntry.getType()) + ")";

try (PreparedStatement preparedStatement = connection.prepareStatement(query,
new String[] {REMOTE_ID.toLowerCase()})) { // This is the only method to get generated keys which is accepted by MySQL, PostgreSQL and Oracle.
new String[] {ENTRY_REMOTE_ID.toLowerCase()})) { // This is the only method to get generated keys which is accepted by MySQL, PostgreSQL and Oracle.
preparedStatement.executeUpdate();
try (ResultSet generatedKeys = preparedStatement.getGeneratedKeys()) {
if (generatedKeys.next()) {
Expand All @@ -208,12 +179,8 @@ public void insertEntry(BibEntry bibEntry) {
public void updateEntry(BibEntry bibEntry, String field, String newValue) {
prepareEntryTableStructure(bibEntry);
String query = "UPDATE " + escape(ENTRY, dbType) + " SET " + escape(field.toUpperCase(), dbType) + " = "
+ escapeValue(newValue) + " WHERE " + escape(REMOTE_ID, dbType) + " = " + bibEntry.getRemoteId();
try {
connection.createStatement().executeUpdate(query);
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
}
+ escapeValue(newValue) + " WHERE " + escape(ENTRY_REMOTE_ID, dbType) + " = " + bibEntry.getRemoteId();
executeUpdate(query);
LOGGER.info("SQL UPDATE: " + query);
}

Expand All @@ -222,13 +189,9 @@ public void updateEntry(BibEntry bibEntry, String field, String newValue) {
* @param bibEntry {@link BibEntry} to be deleted
*/
public void removeEntry(BibEntry bibEntry) {
String query = "DELETE FROM " + escape(ENTRY, dbType) + " WHERE " + escape(REMOTE_ID, dbType) + " = "
String query = "DELETE FROM " + escape(ENTRY, dbType) + " WHERE " + escape(ENTRY_REMOTE_ID, dbType) + " = "
+ bibEntry.getRemoteId();
try {
connection.createStatement().executeUpdate(query);
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
}
executeUpdate(query);
LOGGER.info("SQL DELETE: " + query);
normalizeEntryTable();
}
Expand All @@ -246,13 +209,8 @@ public void prepareEntryTableStructure(BibEntry bibEntry) {

String columnType = dbType == DBType.ORACLE ? " CLOB NULL" : " TEXT NULL DEFAULT NULL";

try {
for (String fieldName : fieldNames) {
connection.createStatement().executeUpdate(
"ALTER TABLE " + escape(ENTRY, dbType) + " ADD " + escape(fieldName, dbType) + columnType);
}
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
for (String fieldName : fieldNames) {
executeUpdate("ALTER TABLE " + escape(ENTRY, dbType) + " ADD " + escape(fieldName, dbType) + columnType);
}
}

Expand All @@ -263,8 +221,8 @@ public void normalizeEntryTable() {
ArrayList<String> columnsToRemove = new ArrayList<>();

columnsToRemove.addAll(dbHelper.allToUpperCase(dbHelper.getColumnNames(escape(ENTRY, dbType))));
columnsToRemove.remove(REMOTE_ID); // essential column
columnsToRemove.remove(ENTRYTYPE); // essential column
columnsToRemove.remove(ENTRY_REMOTE_ID); // essential column
columnsToRemove.remove(ENTRY_ENTRYTYPE); // essential column

try (ResultSet resultSet = dbHelper.query("SELECT * FROM " + escape(ENTRY, dbType))) {
while (resultSet.next()) {
Expand Down Expand Up @@ -295,12 +253,8 @@ public void normalizeEntryTable() {
columnExpression = "DROP (" + columnExpression + ")"; // DROP command in Oracle differs from the other systems.
}

try {
if (columnsToRemove.size() > 0) {
connection.createStatement().executeUpdate("ALTER TABLE " + escape(ENTRY, dbType) + " " + columnExpression);
}
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
if (columnsToRemove.size() > 0) {
executeUpdate("ALTER TABLE " + escape(ENTRY, dbType) + " " + columnExpression);
}
}

Expand All @@ -316,9 +270,9 @@ public List<BibEntry> getRemoteEntries() {
while (resultSet.next()) {
BibEntry bibEntry = new BibEntry();
for (String column : columns) {
if (column.equals(REMOTE_ID)) { // distinguish, because special methods in BibEntry has to be used in this case
if (column.equals(ENTRY_REMOTE_ID)) { // distinguish, because special methods in BibEntry has to be used in this case
bibEntry.setRemoteId(resultSet.getInt(column));
} else if (column.equals(ENTRYTYPE)) {
} else if (column.equals(ENTRY_ENTRYTYPE)) {
bibEntry.setType(resultSet.getString(column));
} else {
String value = resultSet.getString(column);
Expand Down Expand Up @@ -366,6 +320,14 @@ public String escapeValue(String value) {
return value;
}

public void executeUpdate(String query) {
try {
connection.createStatement().executeUpdate(query);
} catch (SQLException e) {
LOGGER.error("SQL Error: " + e.getMessage());
}
}

public void setConnection(Connection connection) {
this.connection = connection;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/sf/jabref/remote/DBSynchronizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void listen(EntryRemovedEvent event) {
*/
public void initializeDatabases() {

if (!dbProcessor.checkIntegrity()) {
if (!dbProcessor.checkBaseIntegrity()) {
LOGGER.info(Localization.lang("Integrity check failed. Fixing..."));
dbProcessor.setUpRemoteDatabase();
}
Expand Down
24 changes: 0 additions & 24 deletions src/main/java/net/sf/jabref/remote/DBType.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
*/
package net.sf.jabref.remote;

import java.util.HashMap;
import java.util.Map;

/**
* Enumerates all supported database systems (DBS) by JabRef.
*/
Expand All @@ -38,25 +35,4 @@ public String toString() {
return this.type;
}

/**
* Retrieves a mapping of the table structure dependent on the type.
* @return Mapping of columns name and their type
*/
public Map<String, String> getStructure(String table) {
Map<String, String> structure = new HashMap<>();
if (table.equals(DBProcessor.ENTRY)) {
if (type.equals(DBType.MYSQL)) {
structure.put(DBProcessor.REMOTE_ID, "INT");
structure.put(DBProcessor.ENTRYTYPE, "VARCHAR");
} else if (type.equals(DBType.POSTGRESQL)) {
structure.put(DBProcessor.REMOTE_ID, "SERIAL");
structure.put(DBProcessor.ENTRYTYPE, "VARCHAR");
} else if (type.equals(DBType.ORACLE)) {
structure.put(DBProcessor.REMOTE_ID, "NUMBER");
structure.put(DBProcessor.ENTRYTYPE, "VARCHAR2");
}
}
return structure;
}

}
Loading

0 comments on commit d47d740

Please # to comment.