From ce02d4d02ba6e0b9b7f21e190fd7735b40928e1a Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sat, 23 Feb 2019 08:28:49 +0100 Subject: [PATCH] Added reading from files and safety execution. --- src/nl/andrewlalis/DatabaseHelper.java | 54 ++++++++++++++++---- src/nl/andrewlalis/Main.java | 2 +- src/nl/andrewlalis/Window.form | 13 +++++ src/nl/andrewlalis/Window.java | 64 ++++++++++++++++++------ src/nl/andrewlalis/log/ExecutionLog.java | 2 +- src/nl/andrewlalis/util/FileLoader.java | 51 +++++++++++++++++++ 6 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 src/nl/andrewlalis/util/FileLoader.java diff --git a/src/nl/andrewlalis/DatabaseHelper.java b/src/nl/andrewlalis/DatabaseHelper.java index 61ebf2c..9aaea02 100644 --- a/src/nl/andrewlalis/DatabaseHelper.java +++ b/src/nl/andrewlalis/DatabaseHelper.java @@ -11,7 +11,7 @@ import java.util.List; import static nl.andrewlalis.Window.*; -public class DatabaseHelper { +class DatabaseHelper { private String host; private int port; @@ -19,7 +19,7 @@ public class DatabaseHelper { private String password; 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.port = port; this.user = user; @@ -27,7 +27,7 @@ public class DatabaseHelper { 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. Thread t = new Thread(() -> { // Setup both databases. @@ -37,26 +37,26 @@ public class DatabaseHelper { "DROP DATABASE " + DB_TESTING + ";"; String createDatabases = "CREATE DATABASE " + DB_TEMPLATE + "; " + "CREATE DATABASE " + DB_TESTING + ";"; - this.executeQueries("", dropDatabases); - this.executeQueries("", createDatabases); + this.executeQueries("", dropDatabases, false); + this.executeQueries("", createDatabases, false); this.window.unindentOutput(); // Run initialization script on each database. this.window.appendOutput("Running initialization SQL on databases..."); this.window.indentOutput(); - this.executeQueries(DB_TEMPLATE, initializationSQL); - this.executeQueries(DB_TESTING, initializationSQL); + this.executeQueries(DB_TEMPLATE, initializationSQL, false); + this.executeQueries(DB_TESTING, initializationSQL, false); this.window.unindentOutput(); // TESTING SQL HERE // Template-specific output. this.window.setOutputChannel(OUTPUT_TEMPLATE); - ExecutionLog templateLog = this.executeQueries(DB_TEMPLATE, templateSQL); + ExecutionLog templateLog = this.executeQueries(DB_TEMPLATE, templateSQL, true); // Testing-specific output. this.window.setOutputChannel(OUTPUT_TESTING); - ExecutionLog testingLog = this.executeQueries(DB_TESTING, testingSQL); + ExecutionLog testingLog = this.executeQueries(DB_TESTING, testingSQL, true); // Output results. this.window.setOutputChannel(OUTPUT_GENERAL); @@ -65,13 +65,30 @@ public class DatabaseHelper { 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. * @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 safe Whether the queries should be checked for safety. * @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(); String url = String.format( "jdbc:postgresql://%s:%4d/%s?user=%s&password=%s", @@ -93,7 +110,11 @@ public class DatabaseHelper { for (String query : queries) { 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) { window.appendOutput("Exception while executing statement: " + e.getMessage()); } @@ -152,6 +173,17 @@ public class DatabaseHelper { 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) * @param str The string to check. diff --git a/src/nl/andrewlalis/Main.java b/src/nl/andrewlalis/Main.java index fe7077e..e3d6d94 100644 --- a/src/nl/andrewlalis/Main.java +++ b/src/nl/andrewlalis/Main.java @@ -4,7 +4,7 @@ import javax.swing.*; 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) { Window window = new Window(APPLICATION_NAME); diff --git a/src/nl/andrewlalis/Window.form b/src/nl/andrewlalis/Window.form index 6230ccd..2c08ab2 100644 --- a/src/nl/andrewlalis/Window.form +++ b/src/nl/andrewlalis/Window.form @@ -60,6 +60,12 @@ + + + + + + @@ -105,6 +111,12 @@ + + + + + + @@ -210,6 +222,7 @@ + diff --git a/src/nl/andrewlalis/Window.java b/src/nl/andrewlalis/Window.java index 51e882b..0e08e54 100644 --- a/src/nl/andrewlalis/Window.java +++ b/src/nl/andrewlalis/Window.java @@ -1,9 +1,11 @@ package nl.andrewlalis; +import nl.andrewlalis.util.FileLoader; + import javax.swing.*; -import javax.xml.crypto.Data; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.text.DefaultCaret; +import java.io.IOException; public class Window extends JFrame { private JPanel mainPanel; @@ -26,27 +28,34 @@ public class Window extends JFrame { private JButton loadTemplateFromFileButton; private JButton loadTestingFromFileButton; private JButton loadInitializationFromFileButton; + private JButton clearTestingButton; + private JButton clearTemplateButton; - public static final int OUTPUT_GENERAL = 0; - public static final int OUTPUT_TEMPLATE = 1; - public static final int OUTPUT_TESTING = 2; + static final int OUTPUT_GENERAL = 0; + static final int OUTPUT_TEMPLATE = 1; + static final int OUTPUT_TESTING = 2; - public static final String DB_TEMPLATE = "sql_assess_template"; - public static final String DB_TESTING = "sql_assess_testing"; + static final String DB_TEMPLATE = "sql_assess_template"; + static final String DB_TESTING = "sql_assess_testing"; private int outputChannel; private int outputIndent; - public Window(String applicationName) { + Window(String 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.setContentPane(mainPanel); - executeButton.addActionListener(actionEvent -> { - this.executeSQL(); - }); + executeButton.addActionListener(actionEvent -> this.executeSQL()); clearOutputButton.addActionListener(actionEvent -> { this.templateOutputTextArea.setText(null); @@ -54,9 +63,11 @@ public class Window extends JFrame { 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; } } + + /** + * 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())); + } + } } diff --git a/src/nl/andrewlalis/log/ExecutionLog.java b/src/nl/andrewlalis/log/ExecutionLog.java index c27f0c4..11bbe26 100644 --- a/src/nl/andrewlalis/log/ExecutionLog.java +++ b/src/nl/andrewlalis/log/ExecutionLog.java @@ -18,7 +18,7 @@ public class ExecutionLog { this.actions.add(action); } - public List getActions() { + private List getActions() { return this.actions; } diff --git a/src/nl/andrewlalis/util/FileLoader.java b/src/nl/andrewlalis/util/FileLoader.java new file mode 100644 index 0000000..18bc554 --- /dev/null +++ b/src/nl/andrewlalis/util/FileLoader.java @@ -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 ""; + } + } +}