Added reading from files and safety execution.

This commit is contained in:
Andrew Lalis 2019-02-23 08:28:49 +01:00 committed by andrewlalis
parent 9efc91f82f
commit ce02d4d02b
6 changed files with 158 additions and 28 deletions

View File

@ -11,7 +11,7 @@ import java.util.List;
import static nl.andrewlalis.Window.*; import static nl.andrewlalis.Window.*;
public class DatabaseHelper { class DatabaseHelper {
private String host; private String host;
private int port; private int port;
@ -19,7 +19,7 @@ public class DatabaseHelper {
private String password; private String password;
private Window window; private Window window;
public DatabaseHelper(String host, int port, String user, String password, Window window) { DatabaseHelper(String host, int port, String user, String password, Window window) {
this.host = host; this.host = host;
this.port = port; this.port = port;
this.user = user; this.user = user;
@ -27,7 +27,7 @@ public class DatabaseHelper {
this.window = window; this.window = window;
} }
public void executeSQLComparison(String initializationSQL, String templateSQL, String testingSQL) { void executeSQLComparison(String initializationSQL, String templateSQL, String testingSQL) {
// Run the database code in a separate thread to update the UI quickly. // Run the database code in a separate thread to update the UI quickly.
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
// Setup both databases. // Setup both databases.
@ -37,26 +37,26 @@ public class DatabaseHelper {
"DROP DATABASE " + DB_TESTING + ";"; "DROP DATABASE " + DB_TESTING + ";";
String createDatabases = "CREATE DATABASE " + DB_TEMPLATE + "; " + String createDatabases = "CREATE DATABASE " + DB_TEMPLATE + "; " +
"CREATE DATABASE " + DB_TESTING + ";"; "CREATE DATABASE " + DB_TESTING + ";";
this.executeQueries("", dropDatabases); this.executeQueries("", dropDatabases, false);
this.executeQueries("", createDatabases); this.executeQueries("", createDatabases, false);
this.window.unindentOutput(); this.window.unindentOutput();
// Run initialization script on each database. // Run initialization script on each database.
this.window.appendOutput("Running initialization SQL on databases..."); this.window.appendOutput("Running initialization SQL on databases...");
this.window.indentOutput(); this.window.indentOutput();
this.executeQueries(DB_TEMPLATE, initializationSQL); this.executeQueries(DB_TEMPLATE, initializationSQL, false);
this.executeQueries(DB_TESTING, initializationSQL); this.executeQueries(DB_TESTING, initializationSQL, false);
this.window.unindentOutput(); this.window.unindentOutput();
// TESTING SQL HERE // TESTING SQL HERE
// Template-specific output. // Template-specific output.
this.window.setOutputChannel(OUTPUT_TEMPLATE); this.window.setOutputChannel(OUTPUT_TEMPLATE);
ExecutionLog templateLog = this.executeQueries(DB_TEMPLATE, templateSQL); ExecutionLog templateLog = this.executeQueries(DB_TEMPLATE, templateSQL, true);
// Testing-specific output. // Testing-specific output.
this.window.setOutputChannel(OUTPUT_TESTING); this.window.setOutputChannel(OUTPUT_TESTING);
ExecutionLog testingLog = this.executeQueries(DB_TESTING, testingSQL); ExecutionLog testingLog = this.executeQueries(DB_TESTING, testingSQL, true);
// Output results. // Output results.
this.window.setOutputChannel(OUTPUT_GENERAL); this.window.setOutputChannel(OUTPUT_GENERAL);
@ -65,13 +65,30 @@ public class DatabaseHelper {
t.start(); t.start();
} }
// private void listDatabases() {
// try {
// PreparedStatement ps = connection
// .prepareStatement("SELECT datname FROM pg_database WHERE datistemplate = false;");
// ResultSet rs = ps.executeQuery();
// while (rs.next()) {
// System.out.println(rs.getString(1));
// }
// rs.close();
// ps.close();
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
/** /**
* Executes possibly many queries which are contained in one string. * Executes possibly many queries which are contained in one string.
* @param database The database name to connect to, or an empty string to connect to the user's database. * @param database The database name to connect to, or an empty string to connect to the user's database.
* @param queriesString The string of queries. * @param queriesString The string of queries.
* @param safe Whether the queries should be checked for safety.
* @return The execution log from this series of queries. * @return The execution log from this series of queries.
*/ */
public ExecutionLog executeQueries(String database, String queriesString) { private ExecutionLog executeQueries(String database, String queriesString, boolean safe) {
ExecutionLog executionLog = new ExecutionLog(); ExecutionLog executionLog = new ExecutionLog();
String url = String.format( String url = String.format(
"jdbc:postgresql://%s:%4d/%s?user=%s&password=%s", "jdbc:postgresql://%s:%4d/%s?user=%s&password=%s",
@ -93,7 +110,11 @@ public class DatabaseHelper {
for (String query : queries) { for (String query : queries) {
try { try {
executionLog.recordAction(executeQuery(query, st)); if (!safe || isQuerySafe(query)) {
executionLog.recordAction(executeQuery(query, st));
} else {
window.appendOutput("Blocked execution of unsafe query: " + query);
}
} catch (SQLException e) { } catch (SQLException e) {
window.appendOutput("Exception while executing statement: " + e.getMessage()); window.appendOutput("Exception while executing statement: " + e.getMessage());
} }
@ -152,6 +173,17 @@ public class DatabaseHelper {
return strings; return strings;
} }
/**
* Determines if the given query is safe to run.
* @param query The query to run.
* @return True if this query is safe, or false if it would cause damage to the system.
*/
private static boolean isQuerySafe(String query) {
String upper = query.trim().toUpperCase();
return !upper.startsWith("CREATE DATABASE")
&& !upper.startsWith("DROP DATABASE");
}
/** /**
* Determines if an SQL string is a query (it should return a result set) * Determines if an SQL string is a query (it should return a result set)
* @param str The string to check. * @param str The string to check.

View File

@ -4,7 +4,7 @@ import javax.swing.*;
public class Main { public class Main {
public static final String APPLICATION_NAME = "SQL-Assesser"; private static final String APPLICATION_NAME = "SQL-Assesser";
public static void main(String[] args) { public static void main(String[] args) {
Window window = new Window(APPLICATION_NAME); Window window = new Window(APPLICATION_NAME);

View File

@ -60,6 +60,12 @@
<text value="Load From File"/> <text value="Load From File"/>
</properties> </properties>
</component> </component>
<component id="a7f77" class="javax.swing.JButton" binding="clearTemplateButton">
<constraints/>
<properties>
<text value="Clear"/>
</properties>
</component>
</children> </children>
</grid> </grid>
</children> </children>
@ -105,6 +111,12 @@
<text value="Load From File"/> <text value="Load From File"/>
</properties> </properties>
</component> </component>
<component id="2fbec" class="javax.swing.JButton" binding="clearTestingButton">
<constraints/>
<properties>
<text value="Clear"/>
</properties>
</component>
</children> </children>
</grid> </grid>
</children> </children>
@ -210,6 +222,7 @@
<scrollpane id="adb12" binding="outputScrollPane"> <scrollpane id="adb12" binding="outputScrollPane">
<constraints border-constraint="Center"/> <constraints border-constraint="Center"/>
<properties> <properties>
<autoscrolls value="true"/>
<preferredSize width="2" height="300"/> <preferredSize width="2" height="300"/>
<verticalScrollBarPolicy value="22"/> <verticalScrollBarPolicy value="22"/>
</properties> </properties>

View File

@ -1,9 +1,11 @@
package nl.andrewlalis; package nl.andrewlalis;
import nl.andrewlalis.util.FileLoader;
import javax.swing.*; import javax.swing.*;
import javax.xml.crypto.Data; import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.event.ActionEvent; import javax.swing.text.DefaultCaret;
import java.awt.event.ActionListener; import java.io.IOException;
public class Window extends JFrame { public class Window extends JFrame {
private JPanel mainPanel; private JPanel mainPanel;
@ -26,27 +28,34 @@ public class Window extends JFrame {
private JButton loadTemplateFromFileButton; private JButton loadTemplateFromFileButton;
private JButton loadTestingFromFileButton; private JButton loadTestingFromFileButton;
private JButton loadInitializationFromFileButton; private JButton loadInitializationFromFileButton;
private JButton clearTestingButton;
private JButton clearTemplateButton;
public static final int OUTPUT_GENERAL = 0; static final int OUTPUT_GENERAL = 0;
public static final int OUTPUT_TEMPLATE = 1; static final int OUTPUT_TEMPLATE = 1;
public static final int OUTPUT_TESTING = 2; static final int OUTPUT_TESTING = 2;
public static final String DB_TEMPLATE = "sql_assess_template"; static final String DB_TEMPLATE = "sql_assess_template";
public static final String DB_TESTING = "sql_assess_testing"; static final String DB_TESTING = "sql_assess_testing";
private int outputChannel; private int outputChannel;
private int outputIndent; private int outputIndent;
public Window(String applicationName) { Window(String applicationName) {
super(applicationName); super(applicationName);
// Setup autoscrolling on text areas.
DefaultCaret caret = (DefaultCaret) this.outputTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
// Setup default SQL values.
this.fillDefaultSQL();
this.setOutputChannel(OUTPUT_GENERAL); this.setOutputChannel(OUTPUT_GENERAL);
this.setContentPane(mainPanel); this.setContentPane(mainPanel);
executeButton.addActionListener(actionEvent -> { executeButton.addActionListener(actionEvent -> this.executeSQL());
this.executeSQL();
});
clearOutputButton.addActionListener(actionEvent -> { clearOutputButton.addActionListener(actionEvent -> {
this.templateOutputTextArea.setText(null); this.templateOutputTextArea.setText(null);
@ -54,9 +63,11 @@ public class Window extends JFrame {
this.outputTextArea.setText(null); this.outputTextArea.setText(null);
}); });
loadInitializationFromFileButton.addActionListener(actionEvent -> { loadInitializationFromFileButton.addActionListener(actionEvent -> this.fillSQLFromFileChooser(this.initializationTextArea));
loadTemplateFromFileButton.addActionListener(actionEvent -> this.fillSQLFromFileChooser(this.templateTextArea));
}); loadTestingFromFileButton.addActionListener(actionEvent -> this.fillSQLFromFileChooser(this.testingTextArea));
clearTemplateButton.addActionListener(actionEvent -> this.templateTextArea.setText(null));
clearTestingButton.addActionListener(actionEvent -> this.testingTextArea.setText(null));
} }
/** /**
@ -119,4 +130,27 @@ public class Window extends JFrame {
break; break;
} }
} }
/**
* Fills the input elements from the SQL packaged with this application.
*/
private void fillDefaultSQL() {
try {
this.initializationTextArea.setText(FileLoader.readResource("initialization.sql"));
this.templateTextArea.setText(FileLoader.readResource("template.sql"));
this.testingTextArea.setText(FileLoader.readResource("example_test.sql"));
} catch (IOException e) {
this.appendOutput("Could not load default SQL resources.");
e.printStackTrace();
}
}
private void fillSQLFromFileChooser(JTextArea textArea) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("SQL", "sql"));
int result = fileChooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
textArea.setText(FileLoader.readFile(fileChooser.getSelectedFile()));
}
}
} }

View File

@ -18,7 +18,7 @@ public class ExecutionLog {
this.actions.add(action); this.actions.add(action);
} }
public List<ExecutionAction> getActions() { private List<ExecutionAction> getActions() {
return this.actions; return this.actions;
} }

View File

@ -0,0 +1,51 @@
package nl.andrewlalis.util;
import java.io.*;
/**
* Helps with loading files.
*/
public class FileLoader {
/**
* Reads a resource file from the classpath, and returns its contents as a string.
* @param classPath The class path of the resource.
* @return The string containing the contents of the file.
* @throws IOException If an error occurred or the file could not be read.
*/
public static String readResource(String classPath) throws IOException {
String newLine = System.getProperty("line.separator");
InputStream is = FileLoader.class.getClassLoader().getResourceAsStream(classPath);
if (is == null) {
throw new IOException("Could not open input stream to resource.");
}
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder result = new StringBuilder();
boolean flag = false;
for (String line; (line = reader.readLine()) != null; ) {
result.append(flag? newLine: "").append(line);
flag = true;
}
return result.toString();
}
/**
* Reads a file into a string.
* @param selectedFile The file object, as it is often selected by a JFileChooser.
* @return The string containing the file, or an empty string.
*/
public static String readFile(File selectedFile) {
try (BufferedReader reader = new BufferedReader(new FileReader(selectedFile))) {
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append('\n');
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}